Android architecture components and coroutines - android

There is a lot of information out there on architecture components, kotlin and coroutines but nowhere I can find an example using all those things together.
I'm struggling on how to use android's architecture components as described here together with coroutines. I have an idea but feel uncertain if it's the correct way of implementating this architectural style.
I'm trying to use the view model + repository pattern together with retro fit and coroutines.
I have the following repository:
class FooRepostiroy(private val fooHttpService: FooHttpService) {
suspend fun someMethod() : SomeResult {
val response = fooHttpService.someRemotCall() // which is also a suspending method using retrofit-2
// process response, store it using room and return SomeResult data object
Then I use the FooRepository from my ViewModel but because someMethod is a suspending method I need to wrap it in a coroutine scope:
class FooViewModel(private val fooRepositoru : FooRepository) : ViewModel() {
private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
override fun onCleared() {
super.onCleared()
someMethodJob?.cancel()
}
Then in the fragment or activity I can observe the view model result
fooViewModel.result.observe(viewLifecycleOwner, Observer {
Starting from my repository layer and below everything can be a suspending function. Then from the view model I can call any suspending function but never have a publicly exposed suspending function in my view model.
Is this the correct or proper way to incorporate coroutines with the view model architecture ?

Is this the correct or proper way to incorporate coroutines with the view model architecture?
Yes!
Every instance of ViewModel has its own ViewModelScope.
The purpose of ViewModelScope is to run the jobs during the life cycle of that ViewModel and take care of automatic cancelation of running coroutine jobs in case the parent Activity/Fragment of ViewModel is destroyed.
Any running jobs under ViewModelScope will be canceled when the ViewModel will be destroyed.
Read more here

private var someMethodJob : Job? = null
val result : MutableLiveData<SomeResult> = MutableLiveData()
fun someMethod() {
someMethodJob = viewModelScope.launch {
result.value = fooRepositoru.someMethod()
}
}
You can ditch all of that and just say
val result: LiveData<SomeResult> = liveData {
emit(fooRepository.someMethod())
}
And then observe result.

Related

Emitting ui state while collecting does not update ui

This init block is in my ViewModel:
init {
viewModelScope.launch {
userRepository.login()
userRepository.user.collect {
_uiState.value = UiState.Success(it)
}
}
}
This is very similar to what's actually written on the app, but even this simple example doesn't work. After userRepository.login(), user which is a SharedFlow emits a new user state. This latest value DOES get collected within this collect function shown above, but when emitting a new uiState containing the result, the view does not get such update.
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Doing this for some reason, does not work. I suspect the issue is related to the lifecycle of the viewmodel, because when I treat the viewmodel as a singleton, this doesn't happen. It happens only when the viewmodel gets destroyed and then created a 2nd (or more) time(s).
What I'm trying to achieve is that the screen containing the view model is aware of the user state. Meaning that when I navigate to the screen, I want it to collect the latest user state, and then decide which content to show.
I also realize this is not the best pattern, most likely. I'm currently looking into a solution that holds the User as part of the app state and collecting per screen (given that it basically changes all or many screens and functionalities) so if you have any resources on an example on such implementation I'd be thankful. But I can't get my head around why this current implementation doesn't work so any light shed on the situation is much appreciated.
EDIT
This is what I have in mind for the repository
private val _user = MutableSharedFlow<User>()
override val user: Flow<User> = _user
override suspend fun login() {
delay(2000)
_user.emit(LoggedUser.aLoggedUser())
}
override suspend fun logout() {
delay(2000)
_user.emit(GuestUser)
}
For your case better to use this pattern:
ViewModel class:
sealed interface UserUiState {
object NotLoggedIn : UserUiState
object Error : UserUiState
data class LoggedIn(val user: User) : UserUiState
}
class MyViewModel #Inject constructor(
userRepository: UserRepository
) : ViewModel() {
val userUiState = userRepository.login()
.map { user ->
if (user != null)
UserUiState.LoggedIn(user)
else
UserUiState.Error
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = UserUiState.NotLoggedIn
)
}
Repository class:
class UserRepository {
fun login(): Flow<User?> = flow {
val user = TODO("Your code to get user")
if (isSuccess) {
emit(user)
} else {
emit(null)
}
}
}
Your screen Composable:
#Composable
fun Screen() {
val userUiState by viewModel.userUiState.collectAsStateWithLifecycle()
when (userUiState) {
is UserUiState.LoggedIn -> { TODO("Success code") }
UserUiState.NotLoggedIn -> { TODO("Waiting for login code") }
UserUiState.Error -> { TODO("Error display code") }
}
}
How it works: login() in repository returns autorized user flow which can be used in ViewModel. I use UserUiState sealed class to handle possible user states. And then I convert User value in map {} to UserUiState to display it in the UI Layer. Then Flow of UserUiState needs to be converted to StateFlow to obtain it from the Composable function, so I made stateIn.
And of course, this will solve your problem
Tell me in the comments if I got something wrong or if the code does not meet your expectations
Note: SharedFlow and StateFlow are not used in the Data Layer like you do.
EDIT:
You can emiting flow like this if you are working with network:
val user = flow of {
while (true) {
// network call to get user
delay(2000)
}
}
If you use Room you can do this in your dao.
#Query(TODO("get actual user query"))
fun getUser(): Flow<User>
It is a better way and it recommended by android developers YouTube channel

RunBlocking with Dispatcher communication not working

I have switched my context to Dispatcher.Main to show UI but data fetched on runBlocking but unable to display in RecylerView
runBlocking {
var fruits = fetchFruitList()
withContext(Dispatchers.Main){
recyclerView.adapter = FruitAdapter(fruits);
}
}
what am I doing wrong and what is the appropriate way to return data from one Dispatcher to another.
I have tried another way
GlobalScope.launch {
withContext(Dispatchers.IO){
var fruits = arrayOf("Grapes","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple").toList()
return#withContext
}
recyclerView.adapter = FruitAdapter(fruits)
}
but in above way I have to declare fruits as global whereas I don't want to have it global to work. Is there a way to return data from one 'dispatcher queue to another
I have to fetch data from Api (IO operation) and display that data in RecyclerView(Main Thread Operation)
That's because you must switch the context after the data is fetched:
GlobalScope.launch(Dispatchers.IO){
var fruits = arrayOf("Grapes","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple",
"Pomegrante","Apple","Mango","TuttiFruit","PineApple").toList()
withContext(Dispatchers.MAIN){
recyclerView.adapter = FruitAdapter(fruits)
}
}
Edit According to the comments:
For the runBlocking check out the documentation first paragraph.
Runs a new coroutine and blocks the current thread interruptibly until
its completion. This function should not be used from a coroutine. It
is designed to bridge regular blocking code to libraries that are
written in suspending style, to be used in main functions and in
tests.
Secondly, you ask for the GlobalScope usage. Yea, if you are doing coroutines in Android you should avoid that. Reasons here.
How to launch a coroutine in Android Activity/Fragment?
First I suggest to use in the ViewModel or the Presenter but if you want to launch a coroutine in the Activity/Fragment, you will need a way to control and manage it's cancellation to avoid memory leak.
The solution for this would be:
private val job: Job = Job()
//or Dispatchers.IO
private val fragmentScope = CoroutineScope(Dispatchers.MAIN + job)
//launch a coroutine later in Activity/Fragment
fragmentScope.launch{
//the default coroutine dispatcher would be the defined dispatcher above
}
override fun onDestroy(){
super.onDestroy()
fragmentScope.cancel()
}
As for your question:
what am I doing wrong and what is the appropriate way to return data
from one Dispatcher to another
You can also try this solution if you want to return values from a different context:
someScope.launch(Dispatchers.MAIN){
var data = withContext(Dispatchers.IO){
val someData = fetchSomeData()
return#withContext data
}
if(data.isAvailable()){ //for example
//runing on the main thread
}
}

How get result to UI Thread from an android kotlin coroutines

I didn't understand how kotlin coroutines work.
I need to do a long work on an asynchronous thread and get the result on the UI Thread in an Android app.
Can someone give me some examples?
For example
private fun getCountries(){
viewModelScope.launch {
val a = model.getAllCountries()
countriesList.value = a
}
}
will lunch model.getAllCountries() async but in the end how can i get result to UI Thread?
Well! Adding to #ianhanniballake's answer,
In your function,
private fun getCountries(){
// 1
viewModelScope.launch {
val a = model.getAllCountries()
countriesList.value = a
}
}
You have launched your suspend function from viewModel scope, and the default context is the main thread.
Now the thread on which suspend fun getAllCountries will work will be specified in the definition of getAllCountries function.
So it can be written something like
suspend fun getAllCountries(): Countries {
// 2
return withContext(Disptachers.IO) {
service.getCountries()
}
}
We specify a new thread to call the server using withContext, and after return from withContext block, we are back on main thread.
As per the documentation for viewModelScope:
This scope is bound to Dispatchers.Main.immediate
Where Dispatchers.Main is the Kotlin way of saying 'the main thread'. This means that, by default, all of the code in the launch block runs on the main thread. Your getAllCountries(), if it wants to run on a different thread, would want to use withContext(Disptachers.IO) to move to the IO coroutine dispatcher, as an example.
Therefore in this case, the result of your method is already on the main thread and there's nothing else you need to do.
I need to do a long work on an asynchronous thread
There's no such thing as an asynchronous thread, actually. Whether your network operations are sync or async gets decided by the implementation of the network API you're using.
If you have a blocking network operation, it will stay blocking even when you apply coroutines. The value of coroutines for that use case is limited to making it a bit easier to transfer the result back to the UI thread.
You achieve this by launching a coroutine with the UI dispatcher (the default) and then switching to a thread pool to perform a blocking operation without blocking the UI thread:
viewModelScope.launch {
countriesList.value = withContext(Dispatchers.IO) {
model.getAllCountries()
}
}
Note that a thread inside the thread pool underlying the IO dispatcher will still be blocked, so in terms of the usage of system resources this doesn't make a difference. There will be as many blocked native threads as there are concurrent network calls.
Another solution would be to post your result within a MutableLiveData inside your ViewModel class and observe the LiveData in your view.
Your ViewModel class:
class CountriesViewModel : ViewModel() {
private val parentJob = Job()
val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default
val viewModelScope = CoroutineScope(coroutineContext)
val countries: MutableLiveData<ArrayList<Country>> = MutableLiveData()
val model = MyModel()
fun getCountries(){
viewModelScope.launch {
val countriesList = model.getAllCountries()
countries.postValue(countries)
}
}
}
Your view class (E.g. a fragment)
class CountriesFragment : Fragment(){
private lateinit var countriesVM : CountriesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
countriesVM = ViewModelProviders.of(this).get(CountriesViewModel::class.java)
// calling api in your view model here
countriesVM.getCountries()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// observer is notified of the changes on countries livedata
countriesVM.countries.observe(this, Observer { countries ->
// Update ui here
updateUI(countries)
})
}
}

Testing LiveData Transformations?

I've built a Splash Screen using Android Architecture Components and Reactive approach.
I return from Preferences LiveData object fun isFirstLaunchLD(): SharedPreferencesLiveData<Boolean>.
I have ViewModel that passes LiveData to the view and updates Preferences
val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
if (isFirstLaunch) {
preferences.isFirstLaunch = false
}
isFirstLaunch
}
In my Fragment, I observe LiveData from ViewModel
viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
if (isFirstLaunch) {
animationView.playAnimation()
} else {
navigateNext()
}
})
I would like to test my ViewModel now to see if isFirstLaunch is updated properly. How can I test it? Have I separated all layers correctly? What kind of tests would you write on this sample code?
Have I separated all layers correctly?
The layers seem reasonably separated. The logic is in the ViewModel and you're not referring to storing Android Views/Fragments/Activities in the ViewModel.
What kind of tests would you write on this sample code?
When testing your ViewModel you can write instrumentation or pure unit tests on this code. For unit testing, you might need to figure out how to make a test double for preferences, so that you can focus on the isFirstLaunch/map behavior. An easy way to do that is passing a fake preference test double into the ViewModel.
How can I test it?
I wrote a little blurb on testing LiveData Transformations, read on!
Testing LiveData Transformations
Tl;DR You can test LiveData transformation, you just need to make sure the result LiveData of the Transformation is observed.
Fact 1: LiveData doesn't emit data if it's not observed. LiveData's "lifecycle awareness" is all about avoiding extra work. LiveData knows what lifecycle state it's observers (usually Activities/Fragments) are in. This allows LiveData to know if it's being observed by anything actually on-screen. If LiveData aren't observed or if their observers are off-screen, the observers are not triggered (an observer's onChanged method isn't called). This is useful because it keeps you from doing extra work "updating/displaying" an off-screen Fragment, for example.
Fact 2: LiveData generated by Transformations must be observed for the transformation to trigger. For Transformation to be triggered, the result LiveData (in this case, isFirstLaunch) must be observed. Again, without observation, the LiveData observers aren't triggered, and neither are the transformations.
When you're unit testing a ViewModel, you shouldn't have or need access to a Fragment/Activity. If you can't set up an observer the normal way, how do you unit test?
Fact 3: In your tests, you don't need a LifecycleOwner to observe LiveData, you can use observeForever You do not need a lifecycle observer to be able to test LiveData. This is confusing because generally outside of tests (ie in your production code), you'll use a LifecycleObserver like an Activity or Fragment.
In tests you can use the LiveData method observeForever() to observer without a lifecycle owner. This observer is "always" observing and doesn't have a concept of on/off screen since there's no LifecycleOwner. You must therefore manually remove the observer using removeObserver(observer).
Putting this all together, you can use observeForever to test your Transformations code:
class ViewModelTest {
// Executes each task synchronously using Architecture Components.
// For tests and required for LiveData to function deterministically!
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
// Create observer - no need for it to do anything!
val observer = Observer<Boolean> {}
try {
// Sets up the state you're testing for in the VM
// This affects the INPUT LiveData of the transformation
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// Observe the OUTPUT LiveData forever
// Even though the observer itself doesn't do anything
// it ensures any map functions needed to calculate
// isFirstLaunch will be run.
viewModel.isFirstLaunch.observeForever(observer)
assertEquals(viewModel.isFirstLaunch.value, true)
} finally {
// Whatever happens, don't forget to remove the observer!
viewModel.isFirstLaunch.removeObserver(observer)
}
}
}
A few notes:
You need to use InstantTaskExecutorRule() to get your LiveData updates to execute synchronously. You'll need the androidx.arch.core:core-testing:<current-version> to use this rule.
While you'll often see observeForever in test code, it also sometimes makes its way into production code. Just keep in mind that when you're using observeForever in production code, you lose the benefits of lifecycle awareness. You must also make sure not to forget to remove the observer!
Finally, if you're writing a lot of these tests, the try, observe-catch-remove-code can get tedious. If you're using Kotlin, you can make an extension function that will simplify the code and avoid the possibility of forgetting to remove the observer. There are two options:
Option 1
/**
* Observes a [LiveData] until the `block` is done executing.
*/
fun <T> LiveData<T>.observeForTesting(block: () -> Unit) {
val observer = Observer<T> { }
try {
observeForever(observer)
block()
} finally {
removeObserver(observer)
}
}
Which would make the test look like:
class ViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// observeForTesting using the OUTPUT livedata
viewModel.isFirstLaunch.observeForTesting {
assertEquals(viewModel.isFirstLaunch.value, true)
}
}
}
Option 2
#VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this#getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
#Suppress("UNCHECKED_CAST")
return data as T
}
Which would make the test look like:
class ViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Test
fun isFirstLaunchTest() {
viewModel.someMethodThatAffectsFirstLaunchLiveData()
// getOrAwaitValue using the OUTPUT livedata
assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), true)
}
}
These options were both taken from the reactive branch of Architecture Blueprints.
It depends on what your SharedPreferencesLiveData does.
If the SharedPreferencesLiveData contains Android specific classes, you won't be able to test this correctly because JUnit won't have access to the Android specific classes.
The other issue is that to be able to observe LiveData, you need some kind of Lifecycle owner. (The this in the original post code.)
In the Unit test, the 'this' can simply be replaced with something like the following:
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
And then used in the following way:
#RunWith(MockitoJUnitRunner::class)
class ViewModelTest {
#Rule
#JvmField
val liveDataImmediateRule = InstantTaskExecutorRule()
#Test
fun viewModelShouldLoadAttributeForConsent() {
var isLaunchedEvent: Boolean = False
// Pseudo code - Create ViewModel
viewModel.isFirstLaunch.observe(lifecycle(), Observer { isLaunchedEvent = it } )
assertEquals(true, isLaunchedEvent)
}
private fun lifecycle(): Lifecycle {
val lifecycle = LifecycleRegistry(Mockito.mock(LifecycleOwner::class.java))
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
return lifecycle
}
}
Note: You have to have the Rule present so that the LiveData executes instantly instead of whenever it wants to.

Coroutines: run a Deferred on a specific CoroutineContext

I'm giving a try to Kotlin Coroutines inside an Android app, specifically I've imported Kotlin Coroutine Adapter for Retrofit.
Kotlin Coroutine Adapter changes Retrofit interface to return a Deferred<T> instead of Call<T>.
What I don't understand is how to launch this Deferred in a particular CoroutineContext that I want to. Consider following code:
class MyViewModel #Inject constructor(
private val foo: Foo,
#Named("ui") private val uiContext: CoroutineContext,
#Named("network") private val networkContext: CoroutineContext
) : ViewModel() {
fun performSomeJob(param: String) {
launch(uiContext) {
try {
val response = foo.bar(param).await()
myTextView.setText(response.name)
} catch (error: Throwable) {
Log.e(error)
}
}
}
Where foo.bar(param) returns Deferred<SomeModel>.
This code works, but I'm not sure on what CoroutineContext this foo.bar(param) is being executed (CommonPool??).
How to explicitly specify, that I want foo.bar(param) to be executed in a networkContext?
val response = async(networkContext) { foo.bar(param) }.await()
This code doesn't work, because response is evaluated to Deferred<SomeModel> instead of SomeModel (which I want to achieve).
The foo.bar() call doesn't start another coroutine, it just wraps the native Retrofit Call so that its state changes get propagated to Deferred. Retrofit manages its own threads to perform its operations and this works just as it would without the coroutine wrapper. If you have a specific concern, you can manage it by configuring Retrofit in the usual way.
The only thing that should matter to you is that your coroutine is executing in the UI context.

Categories

Resources