https://developer.android.com/kotlin/flow/stateflow-and-sharedflow

StateFlow and SharedFlow are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers.

StateFlow

StateFlow    is a state-holder observable flow that emits the current and new state updates to its collectors. The current state value can also be read through its value property. To update state and send it to the flow, assign a new value to the    value    property of the MutableStateFlow class.

class LatestNewsViewModel(
   private val newsRepository: NewsRepository
) : ViewModel() {

   // Backing property to avoid state updates from other classes
   private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
   // The UI collects from this StateFlow to get its state updates
   val uiState: StateFlow<LatestNewsUiState> = _uiState

   init {
       viewModelScope.launch {
           newsRepository.favoriteLatestNews
               // Update View with the latest favorite news
               // Writes to the value property of MutableStateFlow,
               // adding a new element to the flow and updating all
               // of its collectors
               .collect { favoriteNews ->
                   _uiState.value = LatestNewsUiState.Success(favoriteNews)
               }
       }
   }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
   data class Success(news: List<ArticleHeadline>): LatestNewsUiState()
   data class Error(exception: Throwable): LatestNewsUiState()
}

The class responsible for updating a MutableStateFlow is the producer, and all classes collecting from the StateFlow are the consumers. Unlike a cold flow built using the flow builder, a StateFlow is hot: collecting from the flow doesn’t trigger any producer code. A StateFlow is always active and in memory, and it becomes eligible for garbage collection only when there are no other references to it from a garbage collection root.

When a new consumer starts collecting from the flow, it receives the last state in the stream and any subsequent states. You can find this behavior in other observable classes like LiveData.

Flow는 cold이지만 StateFlow는 hot이다.

class LatestNewsActivity : AppCompatActivity() {
   private val latestNewsViewModel = // getViewModel()

   override fun onCreate(savedInstanceState: Bundle?) {
       ...
       // Start a coroutine in the lifecycle scope
       lifecycleScope.launch {
           // repeatOnLifecycle launches the block in a new coroutine every time the
           // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
           repeatOnLifecycle(Lifecycle.State.STARTED) {
               // Trigger the flow and start listening for values.
               // Note that this happens when lifecycle is STARTED and stops
               // collecting when the lifecycle is STOPPED
               latestNewsViewModel.uiState.collect { uiState ->
                   // New value received
                   when (uiState) {
                       is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                       is LatestNewsUiState.Error -> showError(uiState.exception)
                   }
               }
           }
       }
   }
}

activity에서 코루틴 사용할때는 lifecycleScope 를 사용한다. viewModel과 다르게 repeatOnLifecycle(Lifecycle.State.STARTED) 를 통해 lifecycle이 STARTED이상의 상태에서 작동하게 한다. StateFlow, Flow는 lifecycle에 자동으로 맞추어져서 소멸되지 않고 background에서도 계속 작동하게된다. 이를 방지 하기 위해서는 repeatOnLifecycle를 앞에서 언급한것과 같이 사용해서 특정 상태의 경우에만 작동하게 한다. viewModel의 경우는 Activity, fragment와는 다르게 lifecycle이 없으므로 그안에서 사용하는 경우는 좀 다르다. 

StateFlow or any other flow does not stop collecting automatically. To achieve the same behavior,you need to collect the flow from a Lifecycle.repeatOnLifecycle block.

Warning: Never collect a flow from the UI directly from launch or the launchIn extension function if the UI needs to be updated. These functions process events even when the view is not visible. This behavior can lead to app crashes. To avoid that, use the repeatOnLifecycle API as shown above.

To convert any flow to a StateFlow, use the    stateIn    intermediate operator.

StateFlow, Flow, and LiveData

StateFlow and LiveData have similarities. Both are observable data holder classes, and both follow a similar pattern when used in your app architecture.

Note, however, that StateFlow and LiveData do behave differently:

  • StateFlow     requires an initial state to be passed in to the constructor, while LiveData does not.
  • LiveData.observe()    automatically unregisters the consumer when the view goes to the STOPPED state, whereas collecting from a StateFlow or any other flow does not stop collecting automatically. To achieve the same behavior,you need to collect the flow from a Lifecycle.repeatOnLifecycle block.

Making cold flows hot using shareIn

StateFlow is a hot flow—it remains in memory as long as the flow is collected or while any other references to it exist from a garbage collection root. You can turn cold flows hot by using the shareIn operator.

callbackFlow에 관해서는 https://developer.android.com/kotlin/flow를 확인해보면 정보를 얻을수 있다.

Using the callbackFlow created in Kotlin flows as an example, instead of having each collector create a new flow, you can share the data retrieved from Firestore between collectors by using shareIn. You need to pass in the following:

  • A CoroutineScope that is used to share the flow. This scope should live longer than any consumer to keep the shared flow alive as long as needed.
  • The number of items to replay to each new collector.
  • The start behavior policy.

SharedFlow

The    shareIn    function returns a SharedFlow, a hot flow that emits values to all consumers that collect from it. 

You can create a SharedFlow without using shareIn. As an example, you could use a SharedFlow to send ticks to the rest of the app so that all the content refreshes periodically at the same time.

// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
   private val externalScope: CoroutineScope,
   private val tickIntervalMs: Long = 5000
) {
   // Backing property to avoid flow emissions from other classes
   private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
   val tickFlow: SharedFlow<Event<String>> = _tickFlow

   init {
       externalScope.launch {
           while(true) {
               _tickFlow.emit(Unit)
               delay(tickIntervalMs)
           }
       }
   }
}

class NewsRepository(
   ...,
   private val tickHandler: TickHandler,
   private val externalScope: CoroutineScope
) {
   init {
       externalScope.launch {
           // Listen for tick updates
           tickHandler.tickFlow.collect {
               refreshLatestNews()
           }
       }
   }

   suspend fun refreshLatestNews() { ... }
   ...
}

https://developer.android.com/kotlin/flow

In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value.      

Flows are built on top of coroutines and can provide multiple values. A flow is conceptually a stream of data that can be computed asynchronously. The emitted values must be of the same type. For example, a Flow<Int> is a flow that emits integer values.

There are three entities involved in streams of data:

  • A    producer    produces data that is added to the stream. Thanks to coroutines, flows can also produce data asynchronously.
  • (Optional)    Intermediaries    can modify each value emitted into the stream or the stream itself.
  • A    consumer    consumes the values from the stream.

Creating a flow

To create flows, use the    flow builder    APIs. The flow builder function creates a new flow where you can manually emit new values into the stream of data using the    emit    function.

The flow builder is executed within a coroutine. Thus, it benefits from the same asynchronous APIs, but some restrictions apply:

  • Flows are sequential. As the producer is in a coroutine, when calling a suspend function, the producer suspends until the suspend function returns. (producer 코루틴 블럭 또는 함수를 다 처리 하고 emit될때까지 suspend된다는 이야기)
  • With the flow builder, the producer cannot emit values from a different CoroutineContext. Therefore, don’t call emit in a different CoroutineContext by creating new coroutines or by using withContext blocks of code. You can use other flow builders such as callbackFlow in these cases. (producer안에서 새로 코루틴을 만들거나 withContext를 이용할수 없다)

Modifying the stream

Learn more about intermediate operators in the Flow reference documentation.

Intermediate operators can be applied one after the other, forming a chain of operations that are executed lazily when an item is emitted into the flow. Note that simply applying an intermediate operator to a stream does not start the flow collection.

Collecting from a flow

 You can learn more about terminal operators in the official flow documentation.

As    collect    is a suspend function, it needs to be executed within a coroutine. It takes a lambda as a parameter that is called on every new value. Since it’s a suspend function, the coroutine that calls    collect    may suspend until the flow is closed.

Collecting the flow triggers the producer that refreshes the latest data and emits the result. the stream of data will be closed when the ViewModel is cleared and viewModelScope is cancelled. (collector, consumer 쪽에서의 scope가 종료, 취소되는 경우 flow는 종료, 취소된다.)

Flow collection can stop for the following reasons:

  • The coroutine that collects is cancelled, as shown in the previous example. This also stops the underlying producer.
  • The producer finishes emitting items. In this case, the stream of data is closed and the coroutine that called collect resumes execution.

 To optimize and share a flow when multiple consumers collect at the same time, use the   shareIn    operator.

Catching unexpected exceptions

The implementation of the producer can come from a third party library. This means that it can throw unexpected exceptions. To handle these exceptions, use the    catch    intermediate operator.

collect operator가 catch operator 이후에 있는 경우 when an exception occurs, the collect lambda isn’t called, as a new item hasn’t been received.

catch can also emit items to the flow. The example repository layer could emit the cached values instead (catch operator안에 emit 메소드를 통해 값을 리턴할수 있다)

Executing in a different CoroutineContext

By default, the producer of a flow builder executes in the CoroutineContext of the coroutine that collects from it(collector의 코루틴 컨텍스트를 기본적으로 producer도 이용하는데 바꾸어야 할때가 생긴다) 기본적으로 viewmodel의 경우 Dispatchers.Main that is used by viewModelScope.

flowOn위에있는 부분만 flowOn CoroutineContext를 이용하게 되고 나머지는 collector coroutine context를 사용하게 된다.

To change the CoroutineContext of a flow, use the intermediate operator    flowOn.    flowOn changes the CoroutineContext of the upstream flow, meaning the producer and any intermediate operators applied before (or above) flowOn. The downstream flow (the intermediate operators after flowOn along with the consumer) is not affected and executes on the CoroutineContext used to collect from the flow. If there are multiple flowOn operators, each one changes the upstream from its current location.

Convert callback-based APIs to flows

callbackFlow    is a flow builder that lets you convert callback-based APIs into flows. As an example, the Firebase Firestore Android APIs use callbacks. 

Unlike the flow builder, callbackFlow allows values to be emitted from a different CoroutineContext with the    send    function or outside a coroutine with the    offer    function.

(자세한 이해를 위해 따로 공부할 필요가 있다.)

참고자료:

https://youtu.be/emk9_tVVLcc

https://youtu.be/VcOGu1Y0Qes

.

.

https://youtu.be/CIvjwIfOG5A

image
image
image
image
image
image
image
image
image

.

.

.

https://youtu.be/xch4aw7hNcY

asynchrony를 수행하는데 발생하는 일반적인 문제점들

image

코루틴과 채널을 사용한 경우의 예시

image

채널을 통과한 데이터가 각각 데이터를 받을 준비된 코루틴에 나누어서 배급되는점을 볼것

image
image
image
image
image

terminal operator collect가 수행되기 전까지는 cold상태이다.

image
image
image
image
image
image
image
image
image
image
image

위와 같이 기존 안드로이드 livedata와 flow를 섞어 사용가능하다 (view 단계까지 flow를 사용할수도 있다)

image
image
image
image
image

위까지는 livedata와 flow를 같이 사용한 경우의 예시이고 아래는 flow만을 이용한 경우의 예시이다.

image
image

.

.

.

필립

https://youtu.be/B_3iTVJT8Zs

image

위그림에서 producer (emit이 있는) 부분과 consumer (collect가 있는) 부분은 하나의 코루틴이다. 그래서 producer부분에서 1초 지연 consumer부분에서 2초지연 그래서 매 엘리먼트 당 3초식 지연된다. 

image

위그림은 buffer()를 이용 producer, consumer를 다른 코루틴으로 분리한것이다. 이경우 producer에서의 지연과 consumer에서의 지연이 동시에 다른 곳에서 생기게 되고 결과적으로는 전체 프로세스 시간은 가장 늦은consumer지연과 같게 된다.  

image

.

.

.

https://youtu.be/Qk2mIpE_riY

stateflow와 livedata의 차이점

– stateflow는 initial 값을 항상 지정해 주어야 한다.

– back stage로 process가 넘어간 뒤에도 state flow를 돌아간다.

– 다양한 terminal operator를 제공한다. map, zip, filter ……

image

launchWhenStarted 는 start 상태에서만 돌아가는 coroutine이 만들어진다. 일반 launch도 있는데 이를 사용하면 background 상태에서도 계속 돌아간다. 

.

.

.

https://youtu.be/RoGAb0iWljg

shared flow

– initial 값이 없어도 된다.

– state flow는 기본적으로 conflation을(처리를 못하는 consumer의 경우 밀린 element를 무시하고 최근 값만 받아들인다) 속성으로 가지나 shared flow는 아니며 모든 element값을 받는다.

– replay가 가능하다. (새로 연결된 최근 consumer의 경우 지난 값을 받아올수 있다.) 

– shareIn(), stateIn() extension function을 이용해서 일반 flow를 shared flow로 바꿀수 있다. 

original source : https://blog.mindorks.com/livedata-setvalue-vs-postvalue-in-android

we can change the UI of an application when there is a change in some data. 

livedata를 통해 쉽게 데이터 변경을 ui에 적용할수 있다.

livedata 설명

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.

In a simple way, LiveData is basically a data holder and it is used to observe the changes of a particular view and then update the corresponding change.

So, LiveData is used to make the task of implementing ViewModel easier. The best part about LiveData is that the data will not be updated when your View is in the background and when the view will come in the foreground, then it will get the updated data only.

Difference between setValue() and postValue()

either you can update the data on the Main thread or you can update the data using the background thread. So, the use-case of setValue and postValue depends on these two situations only.

While using the Main thread to change the data, you should use the   setValue   method of the MutableLiveData class and while using the background thread to change the LiveData, you should use the   postValue   method of the MutableLiveData class.

So, the duty of the   postValue   method is to post or add a task to the main thread of the application whenever there is a change in the value. And the value will be updated whenever the main thread will be executed (여기서 mainthread가 실행될때 이부분이 중요한 부분). So, basically, it is requesting the main thread to set the new updated value and then notify the observers.

While the setValue method is used to set the changed value from the main thread and if there are some live observers to it, then the updated value will also be sent to those observers as well. This setValue method must be called from the main thread.

So, here are some of the points that you must think before using setValue and postValue:

  • If you are working on the main thread, then both setValue and postValue will work in the same manner i.e. they will update the value and notify the observers.
  • If working in some background thread, then you can’t use setValue. You have to use postValue here with some observer. But the interesting thing about postValue is that the value will be change immediately but the notification that is to be sent to observers will be scheduled to execute on the main thread via the event loop with the handler.

Let’s take an example,

// setValue
liveData.setValue("someNewData")
liveData.setValue("againNewData")

// postValue
liveData.postValue("someNewData")
liveData.postValue("againNewData")

In the above example, the setValue is called from the main thread and the postValue is called from some background thread.

Since the setValue is called twice, so the value will be updated twice and the observers will receive the notification regarding the updated data twice.

But for postValue, the value will be updated twice and the number of times the observers will receive the notification depends on the execution of the main thread. For example, if the postValue is called 4 times before the execution of the main thread, then the observer will receive the notification only once and that too with the latest updated data because the notification to be sent is scheduled to be executed on the main thread. So, if you are calling the postValue method a number of times before the execution of the main thread, then the value that is passed lastly i.e. the latest value will be dispatched to the main thread and rest of the values will be discarded.

Another thing to care about postValue is that, if the field on which the postValue is called is not having any observers and after that, you call the getValue, then you will not receive the value that you set in the postValue because you don’t have an observer here.

original source : https://youtu.be/ZE2Jkvnk2Bs

by philipp

20분 분량이며 설명이 매우 좋다.

image

Application을 위와 같이 extend해주고 @HiltaAndroidApp을 통해 이 andorid application 이 hilt 를 사용한다는 것을 표시해준다.

image

안드로이드 application을 extend한경우 manifest에 수정을 위와 같이 해주어야 한다.

image

(ApplicationComponet::class) 라고 해주면 application scope안에 dependencies가 inject 된다. 즉 application 이 작동하는 동안 dependency를 소멸하지 않고 유지된다.

image

위의 경우 string 타입으로 inject된것이 단하나 이므로 특별히 이름을 써주지 않아도 잘 작동한다. 

image

같은 타입의 여러 injection이 필요한경우 @Named를 이용한다.

image
image

위는 activity scope에서 작동하는 injection을 하는 경우를 보여주고 있다.

image

module안에서 android application context가 필요한 경우는 위와 같이 @ApplicationContext를 이용한다.

image
image
image
image

viewModel의 경우 constructor dependency injection을 이용한다. 

image