In an effort to implement 'clean' architecture on android with the mvp pattern it is advised to treat the android framework as a plugin and not leak any android aware dependencies into the presenter layer. Using rxjava, if I have a presenter that is designed to 'push' data to the view layer I may want to have logic like this:
public interface SearchPresenter {
interface ViewLayer {
void updateResults(List<SearchResult> searchResults)
}
void bind(ViewLayer viewLayer);
void unbind();
}
public class SearchPresenterImpl implements SearchPresenter {
ViewLayer viewLayer;
CompositeDisposable compositeDisposable;
@Override
public void bind(ViewLayer viewLayer) {
this.viewLayer = viewLayer;
compositeDisposable = new CompositeDisposable();
compositeDisposable.add(
searchInteractor.getSearchResults()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::refreshView));
}
private void refreshView(List<SearchResult> searchResults) {
//send the results back to the view layer to update a RecyclerView
viewLayer.updateResults(searchResults)
}
@Override
public void unbind() {
compositeDisposable.dispose();
}
However, by observing on 'AndroidSchedulers.mainThread()' this forces a dependency on:
import io.reactivex.android.schedulers.AndroidSchedulers
at which point, my presenter now knows about android and is tied to it; which I want to avoid.
What is the advised way to handle this so that the result is guaranteed to be delivered to the ViewLayer on android's UI (Main) thread while the presenter maintains no dependencies on anything android related?
AndroidSchedulers.mainThread()
uses Android code to schedule actions on the main thread, don't use it directly in your presenter.
instead You can refactor the presenter to take Schedulers in its constructor, in production code use the regular AndroidSchedulers.mainThread()
and Schedulers.io()
, in testing you can just send Schedulers.trampoline()
or Schedulers.immediate()
.
see this pattern in this example: https://github.com/riggaroo/GithubUsersSearchApp/blob/master/app/src/main/java/za/co/riggaroo/gus/presentation/search/UserSearchPresenter.java
and its test class here: https://github.com/riggaroo/GithubUsersSearchApp/blob/8b83095d7a2cc8f3cb69a945224ab4c37cf54a37/app/src/test/java/za/co/riggaroo/gus/presentation/search/UserSearchPresenterTest.java
As pointed in previous answer you can inject Scheduler
in constructor of Presenter
but there is at least two more possibilities.
You can inject
Scheduler
directly tointeractor
and then you don't need to do any manipulation inpresenter
. Then in tests you can mock yourinteractor
and completely forget aboutrxjava
dependency in your tests.You can use
RxJavaPlugins
andRxAndroidPlugins
to override schedulers in tests if you feel that it is fine for you to keepAndroidSchedulers
in interactors.
So define (Kotlin):
object RxJavaPluginHelper {
fun setup(scheduler: Scheduler = Schedulers.trampoline()) {
RxAndroidPlugins.setInitMainThreadSchedulerHandler { _ -> scheduler }
RxJavaPlugins.setComputationSchedulerHandler { scheduler }
RxJavaPlugins.setIoSchedulerHandler { scheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
RxJavaPlugins.setSingleSchedulerHandler { scheduler }
}
fun teardown() {
RxAndroidPlugins.reset()
RxJavaPlugins.reset()
}
}
And then use
companion object {
@BeforeClass
@JvmStatic
fun setup() {
RxJavaPluginHelper.setup()
}
@AfterClass
@JvmStatic
fun teardown() {
RxJavaPluginHelper.teardown()
}
}
Personally I would not fight for removing this line from presenter, as whole idea behind removing Android
imports from Presenter
is to make it portable to different platforms, but since it is unlikely to happen I would treat it as ok.
来源:https://stackoverflow.com/questions/46570795/using-clean-mvp-on-android-with-rxjava-how-can-you-keep-the-presenter-free-of-a