I have this issue ;)
I'm trying to call this usecase, which at the end returns an Observable.
But, despite using schedulers, constantly is being invoked on the main thread. I don't know why:
It looks like this:
class MainViewModel #Inject constructor(private val loadNewsUseCase: LoadNews) : Model {
override fun loadNews() {
loadNewsUseCase.execute(NewsObserver(), "")
}
override fun dispose() {
loadNewsUseCase.dispose()
}
}
class NewsObserver : DisposableObserver<Result>() {
override fun onComplete() {
Log.i("TAG", "")
}
override fun onNext(t: Result) {
Log.i("TAG", "")
}
override fun onError(e: Throwable) {
Log.i("TAG", "")
}
}
-
abstract class UseCase<T, in P>(
private val computationThreadExecutor: ComputationThreadExecutor,
private val mainThreadExecutor: MainThreadExecutor,
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
) {
abstract fun createUseCase(params: P): Observable<T>
fun execute(disposableObserver: DisposableObserver<T>, params: P) {
requireNotNull(disposableObserver)
val observable = createUseCase(params)
.subscribeOn(Schedulers.newThread())
.observeOn(mainThreadExecutor.getThread())
val disposable = observable.subscribeWith(disposableObserver)
addDisposable(disposable)
}
private fun addDisposable(disposable: Disposable) {
requireNotNull(disposable)
compositeDisposable.add(disposable)
}
fun dispose() {
!compositeDisposable.isDisposed.apply { compositeDisposable.dispose() }
}
}
UseCase concrete implementation uses DataService to fetch data from api, which looks like this:
open class NewsDataService(private val newsDataProvider: NewsDataProvider) : NewsService {
override fun loadNews(): Observable<Result> {
return Observable.just(newsDataProvider.fetchData())
}
}
Inside NewsDataProvider is normal sync retrofit call.
Problem is, that from every begininning useCase is invoked in the mainThread(). Shouldn't be called in a new thread?
Replace
Observable.just(foo)
with something like
Observable.fromCallable(() -> foo)
Observable.just() creates an observable from the supplied values and you're computing the value on the main thread. fromCallable() takes in a callback that can be invoked on your subscription thread.
Make fetchData return an Observable<Result> (or potentially Single but that would require wider updates in your code). Retrofit supports RxJava.
Related
I am writing unit tests for my viewModel, but having trouble executing the tests. The runBlocking { ... } block doesn't actually wait for the code inside to finish, which is surprising to me.
The test fails because result is null. Why doesn't runBlocking { ... } run the launch block inside the ViewModel in blocking fashion?
I know if I convert it to a async method that returns a Deferred object, then I can get the object by calling await(), or I can return a Job and call join(). But, I'd like to do this by leaving my ViewModel methods as void functions, is there a way to do this?
// MyViewModel.kt
class MyViewModel(application: Application) : AndroidViewModel(application) {
val logic = Logic()
val myLiveData = MutableLiveData<Result>()
fun doSomething() {
viewModelScope.launch(MyDispatchers.Background) {
System.out.println("Calling work")
val result = logic.doWork()
System.out.println("Got result")
myLiveData.postValue(result)
System.out.println("Posted result")
}
}
private class Logic {
suspend fun doWork(): Result? {
return suspendCoroutine { cont ->
Network.getResultAsync(object : Callback<Result> {
override fun onSuccess(result: Result) {
cont.resume(result)
}
override fun onError(error: Throwable) {
cont.resumeWithException(error)
}
})
}
}
}
// MyViewModelTest.kt
#RunWith(RobolectricTestRunner::class)
class MyViewModelTest {
lateinit var viewModel: MyViewModel
#get:Rule
val rule: TestRule = InstantTaskExecutorRule()
#Before
fun init() {
viewModel = MyViewModel(ApplicationProvider.getApplicationContext())
}
#Test
fun testSomething() {
runBlocking {
System.out.println("Called doSomething")
viewModel.doSomething()
}
System.out.println("Getting result value")
val result = viewModel.myLiveData.value
System.out.println("Result value : $result")
assertNotNull(result) // Fails here
}
}
What you need to do is wrap your launching of a coroutine into a block with given dispatcher.
var ui: CoroutineDispatcher = Dispatchers.Main
var io: CoroutineDispatcher = Dispatchers.IO
var background: CoroutineDispatcher = Dispatchers.Default
fun ViewModel.uiJob(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(ui) {
block()
}
}
fun ViewModel.ioJob(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(io) {
block()
}
}
fun ViewModel.backgroundJob(block: suspend CoroutineScope.() -> Unit): Job {
return viewModelScope.launch(background) {
block()
}
}
Notice ui, io and background at the top. Everything here is top-level + extension functions.
Then in viewModel you start your coroutine like this:
uiJob {
when (val result = fetchRubyContributorsUseCase.execute()) {
// ... handle result of suspend fun execute() here
}
And in test you need to call this method in #Before block:
#ExperimentalCoroutinesApi
private fun unconfinifyTestScope() {
ui = Dispatchers.Unconfined
io = Dispatchers.Unconfined
background = Dispatchers.Unconfined
}
(Which is much nicer to add to some base class like BaseViewModelTest)
As others mentioned, runblocking just blocks the coroutines launched in it's scope, it's separate from your viewModelScope.
What you could do is to inject your MyDispatchers.Background and set the mainDispatcher to use dispatchers.unconfined.
As #Gergely Hegedus mentions above, the CoroutineScope needs to be injected into the ViewModel. Using this strategy, the CoroutineScope is passed as an argument with a default null value for production. For unit tests the TestCoroutineScope will be used.
SomeUtils.kt
/**
* Configure CoroutineScope injection for production and testing.
*
* #receiver ViewModel provides viewModelScope for production
* #param coroutineScope null for production, injects TestCoroutineScope for unit tests
* #return CoroutineScope to launch coroutines on
*/
fun ViewModel.getViewModelScope(coroutineScope: CoroutineScope?) =
if (coroutineScope == null) this.viewModelScope
else coroutineScope
SomeViewModel.kt
class FeedViewModel(
private val coroutineScopeProvider: CoroutineScope? = null,
private val repository: FeedRepository
) : ViewModel() {
private val coroutineScope = getViewModelScope(coroutineScopeProvider)
fun getSomeData() {
repository.getSomeDataRequest().onEach {
// Some code here.
}.launchIn(coroutineScope)
}
}
SomeTest.kt
#ExperimentalCoroutinesApi
class FeedTest : BeforeAllCallback, AfterAllCallback {
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
private val repository = mockkClass(FeedRepository::class)
private var loadNetworkIntent = MutableStateFlow<LoadNetworkIntent?>(null)
override fun beforeAll(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(testDispatcher)
}
override fun afterAll(context: ExtensionContext?) {
Dispatchers.resetMain()
// Reset Coroutine Dispatcher and Scope.
testDispatcher.cleanupTestCoroutines()
testScope.cleanupTestCoroutines()
}
#Test
fun topCafesPoc() = testDispatcher.runBlockingTest {
...
val viewModel = FeedViewModel(testScope, repository)
viewmodel.getSomeData()
...
}
}
I tried the top answer and worked, but I didn't want to go over all my launches and add a dispatcher reference to main or unconfined in my tests. So I ended up adding this code to my base testing class. I am defining my dispatcher as TestCoroutineDispatcher()
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
private val mainThreadDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
Dispatchers.setMain(mainThreadDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
Dispatchers.resetMain()
}
}
in my base test class I have
#ExtendWith(MockitoExtension::class, InstantExecutorExtension::class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
abstract class BaseTest {
#BeforeAll
private fun doOnBeforeAll() {
MockitoAnnotations.initMocks(this)
}
}
I did use the mockk framework that helps to mock the viewModelScope instance like below
https://mockk.io/
viewModel = mockk<MyViewModel>(relaxed = true)
every { viewModel.viewModelScope}.returns(CoroutineScope(Dispatchers.Main))
There are 3 steps that you need to follow.
Add dependency in gradle file.
testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1")
{ exclude ("org.jetbrains.kotlinx:kotlinx-coroutines-debug") }
Create a Rule class MainCoroutineRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description
#ExperimentalCoroutinesApi
class MainCoroutineRule(private val testDispatcher: TestDispatcher = StandardTestDispatcher()) :
TestWatcher() {
override fun starting(description: Description) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description) {
super.finished(description)
Dispatchers.resetMain()
}
}
Modify your test class to use ExperimentalCoroutinesApi runTest and advanceUntilIdle()
#OptIn(ExperimentalCoroutinesApi::class) // New addition
internal class ConnectionsViewModelTest {
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule() // New addition
...
#Test
fun test_abcd() {
runTest { // New addition
...
val viewModel = MyViewModel()
viewModel.foo()
advanceUntilIdle() // New addition
verify { mockObject.footlooseFunction() }
}
}
For explanation on why to do this you can always refer to the codelab https://developer.android.com/codelabs/advanced-android-kotlin-training-testing-survey#3
The problem you are having stems not from runBlocking, but rather from LiveData not propagating a value without an attached observer.
I have seen many ways of dealing with this, but the simplest is to just use observeForever and a CountDownLatch.
#Test
fun testSomething() {
runBlocking {
viewModel.doSomething()
}
val latch = CountDownLatch(1)
var result: String? = null
viewModel.myLiveData.observeForever {
result = it
latch.countDown()
}
latch.await(2, TimeUnit.SECONDS)
assertNotNull(result)
}
This pattern is quite common and you are likely to see many projects with some variation of it as a function/method in some test utility class/file, e.g.
#Throws(InterruptedException::class)
fun <T> LiveData<T>.getTestValue(): T? {
var value: T? = null
val latch = CountDownLatch(1)
val observer = Observer<T> {
value = it
latch.countDown()
}
latch.await(2, TimeUnit.SECONDS)
observeForever(observer)
removeObserver(observer)
return value
}
Which you can call like this:
val result = viewModel.myLiveData.getTestValue()
Other projects make it a part of their assertions library.
Here is a library someone wrote dedicated to LiveData testing.
You may also want to look into the Kotlin Coroutine CodeLab
Or the following projects:
https://github.com/googlesamples/android-sunflower
https://github.com/googlesamples/android-architecture-components
You don't have to change the ViewModel's code, the only change is required to properly set coroutine scope (and dispatcher) when putting ViewModel under test.
Add this to your unit test:
#get:Rule
open val coroutineTestRule = CoroutineTestRule()
#Before
fun injectTestCoroutineScope() {
// Inject TestCoroutineScope (coroutineTestRule itself is a TestCoroutineScope)
// to be used as ViewModel.viewModelScope fro the following reasons:
// 1. Let test fail if coroutine launched in ViewModel.viewModelScope throws exception;
// 2. Be able to advance time in tests with DelayController.
viewModel.injectScope(coroutineTestRule)
}
CoroutineTestRule.kt
#Suppress("EXPERIMENTAL_API_USAGE")
class CoroutineTestRule : TestRule, TestCoroutineScope by TestCoroutineScope() {
val dispatcher = coroutineContext[ContinuationInterceptor] as TestCoroutineDispatcher
override fun apply(
base: Statement,
description: Description?
) = object : Statement() {
override fun evaluate() {
Dispatchers.setMain(dispatcher)
base.evaluate()
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
}
The code will be executed sequentially (your test code, then view model code, then launched coroutine) due to the replaced main dispatcher.
The advantages of the approach above:
Write test code as normal, no need to use runBlocking or so;
Whenever a crash happen in coroutine, that will fail the test (because of cleanupTestCoroutines() called after every test).
You can test coroutine which uses delay internally. For that test code should be run in coroutineTestRule.runBlockingTest { } and advanceTimeBy() be used to move to the future.
Rxjava 2 on error is not called while unit testing . I am using kotlin with rxJava 2 .
method
#SuppressLint("CheckResult")
override fun fetchFilms() {
view.showLoader(true)
disposable = getRetrofitInstance(false).create(API::class.java)
.fetchFilms()
.subscribeOn(io)
.observeOn(main)
.subscribeWith(object : DisposableObserver<Response>() {
override fun onComplete() {
}
override fun onNext(t: Response) {
println(t)
view.setAdapter(t?.results as List<ResultsItem>)
view.showLoader(false)
}
override fun onError(e: Throwable) {
e.printStackTrace()
view.showLoader(false)
view.showError(e.message.toString()) }
})
}
Test
#Test
fun fetchInvalidDataShouldThrowError(){
`when`(api.fetchFilms()).thenReturn(Observable.error(IOException()))
filmsPresenter.fetchFilms()
val subscriber = TestSubscriber<Response>()
subscriber.awaitTerminalEvent(5,TimeUnit.SECONDS)
verify(filmsView, times(1)).showLoader(true)
verify(filmsView).showError("t")
verify(filmsView).showLoader(false)
}
It seems like you're running your code asynchonously in tests as well.
You'd have to synchronise the schedulers you use before.
#Before
fun setupSchedulers() {
RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
}
#Test
fun fetchInvalidDataShouldThrowError() {
`when`(api.fetchFilms()).thenReturn(Observable.error(IOException("t")))
filmsPresenter.fetchFilms()
verify(filmsView).showLoader(true)
verify(filmsView).showError("t")
verify(filmsView).showLoader(false)
}
I had pass repo to Presenter to have same instance API .
here are some changes i have to make
class RepoTest(val api : API) {
fun filmm() : Observable<Response> {
return api.fetchFilms()
}
}
presenter
class FlimsPresenter(private val view: GetFlimsContract.View, repo: RepoTest) : GetFlimsContract.Presenter
method
override fun fetchFilms() {
view.showLoader(true)
disposable = repot.filmm()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<Response>() {
override fun onComplete() {
}
override fun onNext(t: Response) {
println(t)
view.setAdapter(t?.results as List<ResultsItem>)
if (t?.results.isEmpty()) view.showEmptyView(true)
else view.showEmptyView(false)
view.showLoader(false)
}
override fun onError(e: Throwable) {
e.printStackTrace()
view.showLoader(false)
view.showError(e.message.toString())
}
})
}
Finally test
class TestSample {
#Mock
private lateinit var filmsPresenter: GetFlimsContract.Presenter
#Mock
private lateinit var filmsView: GetFlimsContract.View
private lateinit var api: API
#Mock
private lateinit var disposable: Disposable
#Mock
lateinit var repoTest: RepoTest
#Before
fun setup() {
RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { t -> Schedulers.trampoline() }
MockitoAnnotations.initMocks(this)
repoTest = Mockito.mock(RepoTest::class.java)
filmsView = Mockito.mock(GetFlimsContract.View ::class.java)
filmsPresenter = FlimsPresenter(filmsView,repoTest )
}
#Test
fun `should lod item into views`() {
Mockito.`when`(repoTest.filmm()).thenReturn(Observable.just(Response("","",7, listOf(ResultsItem()))))
filmsPresenter.fetchFilms()
Mockito.verify(filmsView).showLoader(true)
Mockito.verify(filmsView).setAdapter(anyList())
Mockito.verify(filmsView).showLoader(false)
}
#Test
fun `should show error if network call fails`(){
Mockito.`when`(repoTest.filmm()).thenReturn(Observable.error(Exception("t")))
filmsPresenter.fetchFilms()
Mockito.verify(filmsView).showLoader(true)
Mockito.verify(filmsView).showError("t")
Mockito.verify(filmsView).showLoader(false)
}
}
your actually creating two API here.
every time fetchFilms() on presenter gets called a new API is created. you can not mock the API in this way.
best guess to fix it is to pass API object as a method parameter to fetchFilms() in the presenter. this way you can decide to provide a real API or a mock one
I have been facing an issue with subjects and TestSchedulers. My tests pass if I use a Trampoline scheduler but for some reason they fail if I use the TestScheduler.
Here's my sample test and relevant classes.
#RunWith(MockitoJUnitRunner::class)
class DemoViewModelTest {
//Error Mocks
private val actionsStream: PublishSubject<DemoContract.ViewEvent> = PublishSubject.create()
private lateinit var viewModel: DemoViewModel
private val handler = mock(DemoContract.Handler::class.java)
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
}
#Test
fun testUpdateCounter() {
actionsStream.onNext(DemoContract.ViewEvent.UpdateClick)
testScheduler.triggerActions()
verify(handler).onUpdate()
}
protected var testScheduler = TestScheduler()
protected var schedulersProvider: SchedulersProvider = object : SchedulersProvider() {
override fun mainThread(): Scheduler {
return testScheduler
}
override fun io(): Scheduler {
return testScheduler
}
override fun computation(): Scheduler {
return testScheduler
}
override fun newThread(): Scheduler {
return testScheduler
}
override fun trampoline(): Scheduler {
return testScheduler
}
override fun single(): Scheduler {
return testScheduler
}
}
}
And my ViewModel class
class DemoViewModel (val schedulersProvider: SchedulersProvider, val handler:DemoContract.Handler) : DemoContract.ViewModel() {
var viewEventsStream: Observable<DemoContract.ViewEvent>? = null
set(value) {
field = value
subscribeToViewEvents()
}
private fun subscribeToViewEvents() {
viewEventsStream?.let {
it.subscribeOn(schedulersProvider.io())
.observeOn(schedulersProvider.mainThread())
.subscribe(object:Observer<DemoContract.ViewEvent>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: DemoContract.ViewEvent) {
onViewEvent(t)
}
override fun onError(e: Throwable) {
}
})
}
}
fun onViewEvent(event: DemoContract.ViewEvent) {
when (event) {
is DemoContract.ViewEvent.UpdateClick -> {
handler.onUpdate()
}
}
}
}
and My Contract class is
interface DemoContract {
abstract class ViewModel
sealed class ViewEvent {
object UpdateClick : ViewEvent()
}
interface Handler{
fun onUpdate()
}
}
A few things about this, If I replace
viewModel.viewEventsStream = actionsStream
with
viewModel.viewEventsStream = Observable.just(DemoContract.ViewEvent.Update)
this test passes.
Can someone throw some light on this, thanks in advance. Here's the minimum runnable project
Thanks a lot #akarnokd for pointing me in the right direction. Turns out this is a race condition between the subscriber for the PublishSubject being added(subscribeActual call) and the onNext Call that I had trigger in my test. onNext call returns before the former happens.
The solution is to explicitly invoke triggerActions twice once immediately after subscription and once after emission.
Change this
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
}
To
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
testScheduler.triggerActions()
}
I have the following UI flow when searching items from a data source:
Display a progress indicator while retrieving from source -> assign livedata to Outcome.loading(true)
Display results -> assign LiveData Outcome.success(results)
Hide progress indicator in -> assign LiveData Outcome.loading(false)
Now the problem is when #2 and #3 are called while the app is in the background. Resuming the app, the LiveData observers are only notified of #3 and not of #2 resulting to non-populated RecyclerView.
What is the correct way of handling this kind of situation?
class SearchViewModel #Inject constructor(
private val dataSource: MusicInfoRepositoryInterface,
private val scheduler: Scheduler,
private val disposables: CompositeDisposable) : ViewModel() {
private val searchOutcome = MutableLiveData<Outcome<List<MusicInfo>>>()
val searchOutcomLiveData: LiveData<Outcome<List<MusicInfo>>>
get() = searchOutcome
fun search(searchText: String) {
Timber.d(".loadMusicInfos")
if(searchText.isBlank()) {
return
}
dataSource.search(searchText)
.observeOn(scheduler.mainThread())
.startWith(Outcome.loading(true))
.onErrorReturn { throwable -> Outcome.failure(throwable) }
.doOnTerminate { searchOutcome.value = Outcome.loading(false) }
.subscribeWith(object : DisposableSubscriber<Outcome<List<MusicInfo>>>() {
override fun onNext(outcome: Outcome<List<MusicInfo>>?) {
searchOutcome.value = outcome
}
override fun onError(e: Throwable) {
Timber.d(e, ".onError")
}
override fun onComplete() {
Timber.d(".onComplete")
}
}).addTo(disposables)
}
override fun onCleared() {
Timber.d(".onCleared")
super.onCleared()
disposables.clear()
}
}
And below is my Outcome class
sealed class Outcome<T> {
data class Progress<T>(var loading: Boolean) : Outcome<T>()
data class Success<T>(var data: T) : Outcome<T>()
data class Failure<T>(val e: Throwable) : Outcome<T>()
companion object {
fun <T> loading(isLoading: Boolean): Outcome<T> = Progress(isLoading)
fun <T> success(data: T): Outcome<T> = Success(data)
fun <T> failure(e: Throwable): Outcome<T> = Failure(e)
}
}
You should not make your loading state a "double" state (true/false).
Your progress state should be dispatch only when loading, then you go either on success or failure state. Never go back to loading state at the end. Doing so you always know which state your view need to display.
if loading -> show loader
if success -> hide loader, show data
if error -> hide loader, show error
Here is an example extract from my Android Conductor + MVVM + Dagger project template, it uses conductor but you can replace conductor controller with fragment or activity, that's the same logic.
sealed class DataRequestState<T> {
class Start<T> : DataRequestState<T>()
class Success<T>(var data: T) : DataRequestState<T>()
class Error<T>(val error: Throwable) : DataRequestState<T>()
}
ViewModel:
#ControllerScope
class HomeControllerViewModel
#Inject
constructor(homeRepositoryManager: HomeRepositoryManager) : BaseControllerViewModel(),
DataFetchViewModel<Home> {
private val _dataFetchObservable: DataRequestLiveData<Home> =
DataRequestLiveData(homeRepositoryManager.home())
override val dataFetchObservable: LiveData<DataRequestState<Home>> = _dataFetchObservable
override fun refreshData() {
_dataFetchObservable.refresh()
}
}
Base data Controller (fragment/activity/conductor):
abstract class BaseDataFetchController<VM, D> :
BaseViewModelController<VM>() where VM : BaseControllerViewModel, VM : DataFetchViewModel<D> {
override fun onViewCreated(view: View) {
super.onViewCreated(view)
viewModel.dataFetchObservable.observe(this, Observer {
it?.let {
when (it) {
is DataRequestState.Start -> dataFetchStart()
is DataRequestState.Success -> {
dataFetchSuccess(it.data)
dataFetchTerminate()
}
is DataRequestState.Error -> {
dataFetchError(it.error)
dataFetchTerminate()
}
}
}
})
}
protected abstract fun dataFetchStart()
protected abstract fun dataFetchSuccess(data: D)
protected abstract fun dataFetchError(throwable: Throwable)
}
Loading state and loaded data should be strictly separate, and you should maintain two live datas and two observers.
That way, loading == false and you'll receive latest data on re-subscription.
Think about it: loading state isn't really an outcome.
I want my NewsListSubscriber to inherit from an RxJava Subscriber which use a generic type but I get a "Type mismatch" error when I call the UseCase execute method. I read many times the generics page from the Kotlin documentation but I can't find the solution.
Here is my UseCase:
abstract class UseCase(private val threadExecutor: IThreadExecutor,
private val postExecutionThread: IPostExecutionThread) {
private var subscription = Subscriptions.empty()
fun execute(UseCaseSubscriber: rx.Subscriber<Any>) {
subscription = buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(UseCaseSubscriber)
}
protected abstract fun buildUseCaseObservable(): Observable<out Any>
fun unsubscribe() {
if (!subscription.isUnsubscribed) {
subscription.unsubscribe()
}
}
}
And here is how I call it:
override fun loadNewsList() {
getNewsListInteractor.execute(NewsListSubscriber())
}
private inner class NewsListSubscriber : rx.Subscriber<List<NewsModel>>() {
override fun onCompleted() {// TODO}
override fun onError(e: Throwable) {// TODO}
override fun onNext(t: List<NewsModel>) {// TODO}
}
The error is
"Type mismatch. Required: rx.Subscriber. Found: Presenters.NewsListPresenter.NewsListSubscriber"
in the "execute(NewsListSubscriber())" line. I tried playing with the "in" and "out" keywords but I still have the same error.
There is actually a better way to solve this problem. I ran into the same issue and a type cast inside every derived subscriber class was not an option.
Just update the abstract UseCase class with an generic type parameter.
abstract class UseCase<T>(private val threadExecutor: IThreadExecutor,
private val postExecutionThread: IPostExecutionThread) {
private var subscription = Subscriptions.empty()
fun execute(UseCaseSubscriber: rx.Subscriber<T>) {
subscription = buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(UseCaseSubscriber)
}
protected abstract fun buildUseCaseObservable(): Observable<T>
fun unsubscribe() {
if (!subscription.isUnsubscribed) {
subscription.unsubscribe()
}
}
}
When you declare your derived UseCase classes, use your concrete type for the generic parameter when calling the super class.
class ConcreteUseCase(val threadExecutor: IThreadExecutor,
val postExecutionThread: IPostExecutionThread)
: UseCase<ConcreteType>(threadExecutor, postExecutionThread)
Doing so, you can use typed Subscribers in your execute call.
getNewsListInteractor.execute(NewsListSubscriber())
...
private inner class NewsListSubscriber : rx.Subscriber<List<NewsModel() {
override fun onCompleted() {// TODO}
override fun onError(e: Throwable) {// TODO}
override fun onNext(t: List<NewsModel>) {// TODO}
}
I found the solution that is pretty simple actually: my NewsListSubscriber class has to extends from rx.Subscriber<Any> instead of rx.Subscriber<MyWantedClass>. It means I need to cast the received objects to the wanted type.
private inner class NewsListSubscriber : DefaultSubscriber<Any>() {
override fun onCompleted() {}
override fun onError(e: Throwable) {}
override fun onNext(t: Any?) {
val newsList = t as List<News>
...
}
}
In Java the cast is done in background but in Kotlin we need to do it ourself.
I also removed all "in" or "out" keywords in my UseCase class.