Why is the getValue of LiveData<Long> Nullable? - kotlin

The Code A is based https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/main/java/com/android/example/livedatabuilder/LiveDataViewModel.kt
I modified some code.
I think that emit(timeStampToTime(currentTime.value)) will be correct, but the system report error, so I have to use emit(timeStampToTime(currentTime.value?:0))
1: I'm very strange that why val currentTime isn't Nullable and currentTime.value is Nullable .
2: Why does the original code use liveData { emit(timeStampToTime(it)) } instead of liveData { emit(timeStampToTime(it?:0)) }
Code A
class LiveDataViewModel(
private val dataSource: DataSource
) : ViewModel() {
val currentTime = dataSource.getCurrentTime()
val currentTimeTransformed: LiveData<String> = liveData {
//emit(timeStampToTime(currentTime.value)) // It will cause error
emit(timeStampToTime(currentTime.value?:0))
}
//Original Code
// Coroutines inside a transformation
// val currentTimeTransformed = currentTime.switchMap {
// timeStampToTime is a suspend function so we need to call it from a coroutine.
// liveData { emit(timeStampToTime(it)) }
// }
private suspend fun timeStampToTime(timestamp: Long): String {
delay(500) // Simulate long operation
val date = Date(timestamp)
return date.toString()
}
}
class DefaultDataSource(private val ioDispatcher: CoroutineDispatcher) : DataSource {
override fun getCurrentTime(): LiveData<Long> =
liveData {
while (true) {
emit(System.currentTimeMillis())
delay(1000)
}
}
...
}

It's nullable because there is a constructor
LiveData()
Creates a LiveData with no value assigned to it.
and calling e.g.
val data: LiveData<Long> = LiveData()
data.value
will return null.
Or to consider your code:
liveData {
while (true) {
emit(System.currentTimeMillis())
delay(1000)
}
}
You emit a value immediately, but if you change the order
liveData {
while (true) {
delay(1000)
emit(System.currentTimeMillis())
}
}
then there is no value emitted during the first second and calling currentTime.value will return null.
(Note: is it intentional that you create a new LiveData on every currentTime access?)

Related

realTime List using callbackFlow from firestore

i'm facing hard times updating list of Orders in real time from firestore using stateflow !!
class RepositoryImp : Repository {
private fun Query.snapshotFlow(): Flow<QuerySnapshot> = callbackFlow {
val snapshott = addSnapshotListener { value, error ->
if (error != null) {
close()
return#addSnapshotListener
}
if (value != null)
trySend(value)
}
awaitClose {
snapshott.remove()
}
}
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshot = ORDER_COLLECTION_REF.snapshotFlow()
.mapNotNull { it.toObjects(OrderModel::class.java) }
emit(State.success(snapshot)) // **HERE** !!!!!!
}.catch {
emit(State.failed(it.message.toString()))
}.flowOn(Dispatchers.IO)
}
i'm receiving the error from // emit(State.success(snapshot)) that says :
Type mismatch: inferred type is Flow<(Mutable)List<OrderModel!>> but List< OrderModel> was expected
sealed class State <T> {
class Loading <T> : State<T>()
data class Success <T> (val data: T) : State <T>()
data class Failed <T> (val message: String) : State <T>()
companion object {
fun <T> loading() = Loading <T>()
fun <T> success(data: T) = Success(data)
fun <T> failed(message: String) = Failed<T>(message)
}
}
My fun to LoadOrders :
private suspend fun loadOrders() {
viewModel.getAllOrders().collect { state ->
when (state) {
is State.Loading -> {
showToast("Loading")
}
is State.Success -> {
adapter.submitList(state.data)
}
is State.Failed -> showToast("Failed! ${state.message}")
}
}
}
Your snapshot variable is a Flow of lists, not a single List. If you want to just fetch the current list, you shouldn't use a flow for that. Instead use get().await().
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshot = ORDER_COLLECTION_REF.get().await()
.let { it.toObjects(OrderModel::class.java) }
emit(State.success(snapshot))
}.catch {
emit(State.failed(it.message.toString()))
}.flowOn(Dispatchers.IO)
The flowOn call is actually unnecessary because we aren't doing anything blocking. await() is a suspend function.
Based on comments discussion below, supposing we want to show a loading state only before the first item, then show a series of success states, and we want to show an error and stop emitting once there's an error, we could do:
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshots = ORDER_COLLECTION_REF.snapshotFlow()
.mapNotNull { State.success(it.toObjects(OrderModel::class.java)) }
emitAll(snapshots)
}.catch {
emit(State.failed(it.message.toString()))
}

Suspend a call until a value is set

I want class UploadWorker to retrieve a value from class Manager, but that value may not yet be ready in Manager. So I want class UploadWorker to wait until that value is set.
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
Manager.isReady()
return Result.success()
}
}
object Manager {
private lateinit var isReady
fun initialize(context: Context, myData: MyData) {
...
isReady = true
}
suspend fun isReady() {
if(::isReady.isInitialized()
return isReady
else // wait here until initialized
}
}
In the else if I could somehow suspend or wait until my MyApplication class calls initialize(). Any ideas?
CompletableDeferred is quite handy for situations like this.
Your code would probably look something like this:
object Manager {
private val initialized = CompletableDeferred<Unit>()
fun initialize(context: Context, myData: MyData) {
...
initialized.complete(Unit)
}
suspend fun awaitInitialized() {
initialized.await()
// initialization is done at this point
}
}
You can use StateFlow to deliver status of initialization:
val isReady = MutableStateFlow(false)
// to wait:
if (isReady.value) return true
else isReady.first { it } // it == true
// to notify:
isReady.value = true
Although StateFlows are quite lightweight, but if you want to do it more lighter but in a ugly way (using coroutine internals directly):
val conts = mutableListOf<Continuation<Boolean>>()
private lateinit var isReady = false
set(value) {
if (value) conts.forEach { it.resume(true) }.also { conts.clear() }
field = value
}
// to wait:
if (isReady) return true
else suspendCancellableCoroutine { cont ->
conts.add(cont)
invokeOnCancellation { conts.remove(cont) }
}
// to notify:
isReady = true

How to subscribe to StateFlow in kotlin-react useEffect

I'm trying to create a small counter example for kotlin-react with functionalComponent with kotlin 1.4-M2.
The example should use kotlinx.coroutines.flow. I'm struggling at collecting the values from the store in reacts useEffect hook.
Store:
object CounterModel { // Modified sample from kotlin StateFlow doc
private val _counter = MutableStateFlow(0) // private mutable state flow
val counter: StateFlow<Int> get() = _counter // publicly exposed as read-only state flow
fun inc() { _counter.value++ }
}
Component:
val counter = functionalComponent<RProps> {
val (counterState, setCounter) = useState(CounterModel.counter.value)
useEffect(listOf()) {
// This does not work
GlobalScope.launch { CounterModel.counter.collect { setCounter(it) } }
}
div {
h1 {
+"Counter: $counterState"
}
button {
attrs.onClickFunction = { CounterModel.inc() }
}
}
}
When I directly call CounterModel.counter.collect { setCounter(it) } it complains about Suspend function 'collect' should be called only from a coroutine or another suspend function.
How would you implement this useEffect hook?
And once the subscription works, how would you unsubscribe from it (use useEffectWithCleanup instead of useEffect)?
Finally found a solution. We can use onEach to do an action for every new value and then 'subscribe' with launchIn. This returns a job that can be canceled for cleanup:
object CounterStore {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> get() = _counter
fun inc() { _counter.value++ }
}
val welcome = functionalComponent<RProps> {
val (counter, setCounter) = useState(CounterStore.counter.value)
useEffectWithCleanup(listOf()) {
val job = CounterStore.counter.onEach { setCounter(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
div {
+"Counter: $counter"
}
button {
attrs.onClickFunction = { CounterStore.inc() }
+"Increment"
}
}
We can extract this StateFlow logic to a custom react hook:
fun <T> useStateFlow(flow: StateFlow<T>): T {
val (state, setState) = useState(flow.value)
useEffectWithCleanup(listOf()) {
val job = flow.onEach { setState(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
return state
}
And use it like this in our component:
val counter = useStateFlow(CounterStore.counter)
The complete project can be found here.
The Flow-Api is very experimental so this might not be the final solution :)
if's very important to check that the value hasn't changed,
before calling setState, otherwise the rendering happens twice
external interface ViewModelProps : RProps {
var viewModel : MyViewModel
}
val App = functionalComponent<ViewModelProps> { props ->
val model = props.viewModel
val (state, setState) = useState(model.stateFlow.value)
useEffectWithCleanup {
val job = model.stateFlow.onEach {
if (it != state) {
setState(it)
}
}.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
}

how to write getter and setter containing mutex.withLock in kotlin

I'd like to have a synchronised access to a variable that represents my state from coroutines. How can I fix this?
private var myState: MyState? = null
get() = mutex.withLock {
return#withLock myState
}
set(value) = mutex.withLock {
field = value
}
private val mutex = Mutex()
right now I get Suspend function 'withLock' should be called only from a coroutine or another suspend function message.
If not possible any alternative elegant solutions?
To call suspend function in a non-suspending context. You can use runBlocking.
private var myState: MyState? = null
get() {
return runBlocking {
mutex.withLock {
myState
}
}
}
set(value) {
runBlocking {
mutex.withLock {
field = value
}
}
}
private val mutex = Mutex()
NOTES:
You might be better off changing the property to two suspend functions (getter/setter), instead of using runBlocking.
All depends on the context in which you call myState.
You also want to consider voting for KT-15555.

Returning a value produced in Kotlin coroutine

I am trying to return a value generated from coroutine
fun nonSuspending (): MyType {
launch(CommonPool) {
suspendingFunctionThatReturnsMyValue()
}
//Do something to get the value out of coroutine context
return somehowGetMyValue
}
I have come up with the following solution (not very safe!):
fun nonSuspending (): MyType {
val deferred = async(CommonPool) {
suspendingFunctionThatReturnsMyValue()
}
while (deferred.isActive) Thread.sleep(1)
return deferred.getCompleted()
}
I also thought about using event bus, but is there a more elegant solution to this problem?
Thanks in advance.
You can do
val result = runBlocking(CommonPool) {
suspendingFunctionThatReturnsMyValue()
}
to block until the result is available.
You can use this:
private val uiContext: CoroutineContext = UI
private val bgContext: CoroutineContext = CommonPool
private fun loadData() = launch(uiContext) {
try {
val task = async(bgContext){dataProvider.loadData("task")}
val result = task.await() //This is the data result
}
}catch (e: UnsupportedOperationException) {
e.printStackTrace()
}
}