Background
Some articles claim that Rx can replace AsyncTask and AsyncTaskLoader.
Seeing that Rx usually makes code shorter, I tried to dive into various samples and ideas of how it works.
The problem
All of the samples and articles I've found, including Github repos, don't seem to really replace AsyncTask and AsyncTaskLoader well:
Can't find how to cancel a task or multiple ones (including interrupting a thread). This is especially important for AsyncTask, which is usually used for RecyclerView and ListView. This can also be done on AsyncTaskLoader, but requires a bit more work than just call "cancel". For AsyncTask, it also provides publishing of a progress, and that's another thing I can't find an example of in Rx.
Can't find how to avoid unsubscribing manually while avoiding memory leaks, which quite ruins the whole point of using Rx, as it's supposed to be shorter. In some samples, there are even more callbacks than normal code has.
What I've tried
Here are some links I've read about Rx:
https://stablekernel.com/replace-asynctask-and-asynctaskloader-with-rx-observable-rxjava-android-patterns/ - this actually provides a cool way to handle AsyncTaskLoader, as it caches the result. However, the samples don't work at all (including building them), as they are very old.
https://code.tutsplus.com/tutorials/getting-started-with-rxjava-20-for-android--cms-28345 , https://code.tutsplus.com/tutorials/reactive-programming-operators-in-rxjava-20--cms-28396 , https://code.tutsplus.com/tutorials/rxjava-for-android-apps-introducing-rxbinding-and-rxlifecycle--cms-28565 - all those talk mostly about RxJava, and not about replacement of AsyncTask or AsyncTaskLoader.
https://github.com/L4Digital/RxLoader - requires unsubscribing, even though it doesn't say it needs it.
The question
How can I use Rx as replacement for AsyncTask and AsyncTaskLoader?
For example, how would I replace this tiny AsyncTaskLoader sample code with Rx equivalent:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportLoaderManager.initLoader(1, Bundle.EMPTY, object : LoaderCallbacks<Int> {
override fun onCreateLoader(id: Int, args: Bundle): Loader<Int> {
Log.d("AppLog", "onCreateLoader")
return MyLoader(this#MainActivity)
}
override fun onLoadFinished(loader: Loader<Int>, data: Int?) {
Log.d("AppLog", "done:" + data!!)
}
override fun onLoaderReset(loader: Loader<Int>) {}
})
}
private class MyLoader(context: Context) : AsyncTaskLoader<Int>(context) {
override fun onStartLoading() {
super.onStartLoading()
forceLoad()
}
override fun loadInBackground(): Int {
Log.d("AppLog", "loadInBackground")
try {
Thread.sleep(10000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
return 123
}
}
}
For AsyncTask, usually we set it per ViewHolder, as a field there, and we cancel it upon onBindViewHolder, and when activity/fragment gets destroyed (go over all those that still pending or running).
Something like this (sample made as short as possible, of course it should be changed depending on needs) :
class AsyncTaskActivity : AppCompatActivity() {
internal val mTasks = HashSet<AsyncTask<Void, Int, Void>>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_async_task)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = object : Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(this#AsyncTaskActivity).inflate(android.R.layout.simple_list_item_1, parent, false))
}
#SuppressLint("StaticFieldLeak")
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.tv.text = "loading..."
if (holder.task != null) {
holder.task!!.cancel(true)
mTasks.remove(holder.task!!)
}
holder.task = object : AsyncTask<Void, Int, Void>() {
override fun doInBackground(vararg voids: Void): Void? {
for (i in 0..100)
try {
Thread.sleep(5)
publishProgress(i)
} catch (e: InterruptedException) {
e.printStackTrace()
}
return null
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
holder.tv.text = "progress:" + values[0]
}
override fun onPostExecute(aVoid: Void?) {
super.onPostExecute(aVoid)
mTasks.remove(holder.task)
holder.tv.text = "done:" + position
holder.task = null
}
}.execute()
}
override fun getItemCount(): Int {
return 1000
}
}
}
override fun onDestroy() {
super.onDestroy()
for (task in mTasks)
task.cancel(true)
}
private class MyViewHolder(itemView: View) : ViewHolder(itemView) {
internal var tv: TextView
internal var task: AsyncTask<Void, Int, Void>? = null
init {
tv = itemView.findViewById(android.R.id.text1)
}
}
}
Let me tell you in advance that my experience in RxJava isn't as some would call it advance. I just know enough to replace AsyncTask using RxJava + RxAndroid. For example, let's convert your AsyncTask code to RxJava2 equivalent using Observable, which is arguably the most popular stream type.
Observable
.create <Int> { emitter ->
try {
for (i in 0..100) {
Thread.sleep(5)
emitter.onNext(i)
}
} catch (e: InterruptedException) {
emitter.onError(e)
}
emitter.onComplete()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ i ->
holder.tv.text = "progress:" + i
}, { e ->
e.printStackTrace()
}, {
holder.tv.text = "done:" + position
})
Now let's go through the code line-by-line:
create lets you customize emitter behavior. Note that this isn't the only method to create the streams, there are also just, fromArray, fromList, defer, etc. We can discuss more of that if you are still interested.
subscribeOn(Schedulers.io()) tells RxJava that #1 will be executed in I/O thread. If this is a background operation, Schedulers.newThread() also works (at least to my knowledge it does).
observeOn(AndroidSchedulers.mainThread()) tells RxJava that #4 will be executed in Android's main thread. This is also where RxAndroid fills the gap since RxJava doesn't have any reference of Android.
subscribe()
onNext(i : Int) - equivalent to publishProgress(). In newer RxJava, emitted value cannot be null.
onError(e : Throwable) - error handling that is long glorified by RxJava's programmers. In newer RxJava, onError() must be provided, doesn't matter if it's empty.
onComplete() - equivalent to onPostExecute().
Again, there might be a lot of misinformation with my answer. I expect someone with better understanding to correct it or provide better answer that this one.
It seems there is no way to implement AsyncTask functionality with RxJava in shorter or more simple form. With RxJava you simplify task chaining, so if you have only one task AsyncTask can be better solution, but when you have several consecutive tasks to do - RxJava's interface is more convenient. So instead of, for example, program onCancelled for each task you should rather implement some common logic.
Related
Below Code
why onnext called only once? When I remove subscribeOn it was called for every number.
when I subscribeOn io thread just once called (for 8658)
can someone explain it to me?
val subject = BehaviorSubject.create<Int>()
subject.onNext(2121)
subject.distinctUntilChanged().doOnNext {
Log.d("AHMET VEFA SARUHAN", it.toString())
}.subscribeOn(Schedulers.io()).subscribe(object : Observer<Int> {
override fun onSubscribe(d: Disposable?) {
}
override fun onNext(t: Int?) {
}
override fun onError(e: Throwable?) {
}
override fun onComplete() {
}
})
subject.onNext(5436)
subject.onNext(8658)
By using subscribeOn, the chain to observe the BehaviorSubject is established concurrently with the thread calling the onNexts. It takes time for a subscribeOn to take effect thus the main thread simply runs ahead and overwrites the subject's current value to the latest in the meantime.
There is no practical reason to use subscribeOn on a Subject in general.
I'm using RxJava and I know about concat, and I guess it does fit to me, because I want to finish first all of first call and then do the second one but I don't know how to implement it.
I have this from now :
private fun assignAllAnswersToQuestion(questionId: Long) {
answerListCreated.forEach { assignAnswerToQuestion(questionId, it.id) }
}
private fun assignAnswerToQuestion(questionId: Long, answerId: Long) {
disposable = questionService.addAnswerToQuestion(questionId,answerId,MyUtils.getAccessTokenFromLocalStorage(context = this))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
result -> //Do nothing it should call the next one
},
{ error -> toast(error.message.toString())}
)
}
But then, once this is finished all of this forEach I'd like to do something like this :
private fun assignAllAnswersToQuestion(questionId: Long) {
answerListCreated.forEach { assignAnswerToQuestion(questionId, it.id)
anotherCallHere(questionId) //Do it when the first forEach is finished!!
}
Any idea?
Also, is a way to do it with coroutines this?
I think you have to .map your list (answerListCreated) to a list of Flowables, and then use Flowable.zip on this list.
zip is used to combine the results of the Flowables into a single result. Since you don't need these results we ignore them.
After zip you are sure that all previous Flowables ended, and you can .flatMap to execute your next call (assuming anotherCallHere returns a Flowable.
In the end, it will be something like:
val flowableList = answerListCreated.map { assignAnswerToQuestion(questionId, it.id) }
disposable = Flowable.zip(flowableList) { /* Ignoring results */ }
.flatMap { anotherCallHere(questionId) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// ...
}
It should be noted that if any of the calls fails, the whole chain will fail (onError will be called).
I'm new to coroutines but I think I can answer for them:
You can use coroutines runBlocking {} for this.
private fun assignAllAnswersToQuestion(questionId: Long) = launch {
runBlocking {
answerListCreated.forEach { assignAnswerToQuestion(questionId, it.id) }
}
anotherCallHere(questionId)
}
private fun assignAnswerToQuestion(questionId: Long, answerId: Long) = launch (Dispatchers.IO) {
questionService.addAnswerToQuestion(
questionId,
answerId,
MyUtils.getAccessTokenFromLocalStorage(context = this)
)
}
launch {} returns a Job object which becomes a child job of the parent coroutine. runBlocking {} will block until all its child jobs have finished, (an alternative is to use launch {}.join() which will have the same affect).
Note that I have made both functions wrap their code in a launch {} block.
To be able to call launch {} like this, you will likely want to make your class implement CoroutineScope
class MyActivityOrFragment: Activity(), CoroutineScope {
lateinit var job = SupervisorJob()
private val exceptionHandler =
CoroutineExceptionHandler { _, error ->
toast(error.message.toString()
}
override val coroutineContext = Dispatchers.Main + job + exceptionHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
...
}
I have to call server through the API call. My API call is AsyncTask with doInBackground() containing server GET. onPostExecute() will get result from server and it will return JSON back to my activity.
Problem is, that I'm using this api call inside the method with return parameter.
Some pseudocode here:
private fun getDataFromServer(apiParam: Int): ArrayList<Objects> {
var extractedObjects: ArrayList<Objects> = ArrayList()
api.getDataByParameter(apiParam, object: APICallback{
override fun onError(errorJSON: JSONObject) {
errorLog: errorJSON
}
override fun onSuccess(resultJSON: JSONObject) {
extractedObjects = getObjectsFromJSON(resultJSON)
}
})
return extractedObjects
}
Adding my API Call from API class (custom class):
fun getDataByParameter(apiParam: Int, callback: APICallback){
class GetDataAsync(private val dataCallback: APICallback): AsyncTask<Void, Void, JSONObject>() {
override fun doInBackground(vararg p0: Void?): JSONObject {
val server = Server.getInstance(context!!)
return server.doGet(apiURL + apiParam.toString() + "/")
}
override fun onPostExecute(result: JSONObject) {
super.onPostExecute(result)
if (result!!.has("data_objects")){
dataCallback.onSuccess(result)
} else {
dataCallback.onError(result)
}
}
}
GetDataAsync(callback).execute()
}
interface APICallback{
fun onError(errorJSON:JSONObject)
fun onSuccess(resultJSON:JSONObject)
}
As you can see, I have to extract objects from JSONObject and convert it to ArrayList<Objects> (sometimes do other stuff like filtering). So what will happen in my program. As I call getDataFromServer it will call AsyncTask to my server and after that it will return empty ArrayList. Until result is available in onSuccess, method is already over.
So I need to wait for onSuccess and also I have to wait for getObjectsFromJSON() to be finished. After that I can return my full list of objects.
Is there any way how to achieve this? I cant do synchronous task, because it will fire NetworkOnMainThreadException. I have to do it in second thread, but also my main thread has to wait for results.
I have to do it in second thread, but also my main thread has to wait for results.
Clearly, these are two contradictory requirements. The point of the NetworkOnMainThreadException isn't the "network" part, but the fact that it's a blocking network call. You aren't allowed to perform any kind of blocking calls on the UI thread, be it network or anything else.
What you must do is arrange for your splash screen to be removed within the onPostExecute call instead of the UI callback method where you show it.
I would highly recommend upgrading your project to Kotlin Coroutines because with them you can avoid all the messy callbacks and write sequential-looking code which nevertheless satisfies the rules of the UI thread. As an outline, this is how your code would look:
class StartActivity : AppCompatActivity, CoroutineContext {
lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterJob = Job()
this.launch {
showSplash()
val result = makeNetworkCall()
updateState(result)
hideSplash()
}
}
suspend fun StartActivity.makeNetworkCall() = withContext(Dispatchers.IO) {
Server.getInstance(this).doGet(apiURL + apiParam.toString() + "/")
}
override fun onDestroy() {
super.onDestroy()
job.cancel() // Automatically cancels all child jobs
}
}
Do the network call inside doInBackground().
get the result from onPostExecute() in the Async task.
I'm currently trying to implement RxLifeCycle into my networking with RxJava. I've been using a subclass of Consumer, but for RxLifeCycle, you need to handle onError. So I have moved over to Observer.
The problem with this is that when the call is disposed, it's calling onComplete instead of onError, which I would prefer.
buildle.gradle:
// RxJava
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.0.3'
compile 'com.trello.rxlifecycle2:rxlifecycle-kotlin:2.2.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-android-lifecycle-kotlin:2.2.1'
My previous NetworkConsumer was structured like this, and I would handle all the results in accept.
NetworkConsumer:
abstract class NetworkConsumer<T> : Consumer<NetworkResponse<T>> {
#Throws(Exception::class)
override fun accept(response: NetworkResponse<T>) {
...
}
// to override
open fun onSuccess(response: T) {}
open fun onComplete() {}
}
My network calls are all structured the same way using Single.
fun getFavorites(): Single<NetworkResponse<Array<MyObject>>>
And I'm using it like this.
service.getFavorites(...)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : NetworkConsumer<Array<MyObject>>() {
override fun onSuccess(response: Array<MyObject>) {
// use response
}
override fun onComplete() {
// do whatever, like hiding the loading view.
loading_view.visibility = View.GONE
}
})
I really like this setup as it allows me to move a lot of the logic from the calling Activity into the NetworkConsumer and only worry about handling the result.
However, with RxLifeCycle, you need to use an Observable instead of a Single. So I created a NetworkObserver to handle this change.
NetworkObserver:
abstract class NetworkObserver<T> : Observer<NetworkResponse<T>> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(response: NetworkResponse<T>) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
// other functions from NetworkConsumer
}
However, the problem is that onComplete is being called when the network call is disposed, which I would prefer to handle any UI changes in onComplete instead.
For example, I'm showing a loading screen when the network call is started, and I want to hide that loading screen when it's done, regardless if it failed or didn't.
I believe I just need to use a different Class instead of Observer for this, but I'm unsure which Class would work best for this.
The correct answer is SingleObserver, this is perfect for networking.
abstract class NetworkObserver<T> : SingleObserver<NetworkResponse<T>> {
override fun onSubscribe(d: Disposable) {
...
}
override fun onSuccess(response: NetworkResponse<T>) {
... handle onSuccess
}
override fun onError(e: Throwable) {
... cancelled or an error
}
}
I got an android project I'm beginning to work on, and I want its structure to be as robust as possible.
I'm coming from a WPF MVVM background and I've been reading a little about android applications architecture, but I just couldn't find a straight clear answer about which architecture I should use.
Some people suggested using MVVM - http://vladnevzorov.com/2011/04/30/android-application-architecture-part-ii-architectural-styles-and-patterns/
and others suggested using MVC, but didn't specify how exactly it should be implemented.
As I said I'm coming from a WPF-MVVM background, and therefore I know it heavily relies on bindings which as far as I understand, are not supported by default in Android.
It seems like there is a 3rd party solution - http://code.google.com/p/android-binding/
But I don't know if I'd like to rely on that. What if its development would stop and it will not be supported by future APIs and etc..
Basically what I'm looking for is a thorough tutorial that will teach me the best practices for building the application's structure. Folders and classes structure and etc. I just couldn't find any thorough tutorial, and I would have expected that Google would supply such a tutorial for its developers. I just don't think that this kind of documentation handles the technical aspect good enough - http://developer.android.com/guide/topics/fundamentals.html
I hope I've been clear enough and that I'm not asking for too much, I just want to be sure about my application's structure, before my code will turn into a spaghetti monster.
Thanks!
First of all, Android doesn't force you to use any architecture. Not only that but it also makes it somewhat difficult to try to follow to any. This will require you to be a smart developer in order to avoid creating a spaghetti codebase :)
You can try to fit in any pattern you know and you like. I find that the best approach will in some way get into your guts as you develop more and more applications (sorry about that but as always, you'll have to make lots of mistakes until you start doing it right).
About the patterns you know, let me do something wrong: I'll mix three different patterns so you get the feeling of what does what in android. I believe the Presenter/ModelView should be somewhere in the Fragment or Activity. Adapters might sometimes do this job as they take care of inputs in lists. Probably Activities should work like Controllers too. Models should be regular java files whereas the View should lay in layout resources and some custom components you might have to implement.
I can give you some tips. This is a community wiki answer so hopefully other people might include other suggestions.
File Organization
I think there are mainly two sensible possibilities:
organize everything by type - create a folder for all activities, another folder for all adapters, another folder for all fragments, etc
organize everything by domain (maybe not the best word). This would mean everything related to "ViewPost" would be inside the same folder - the activity, the fragment, the adapters, etc. Everything related to "ViewPost" would be in another folder. Same for "EditPost", etc. I guess activities would mandate the folders you'd create and then there would be a few more generic ones for base classes for example.
Personally, I have only been involved in projects using the first approach but I really would like to try the later as I believe it could make things more organized. I see no advantage in having a folder with 30 unrelated files but that's what I get with the first approach.
Naming
When creating layouts and styles, always name (or identify them) using a prefix for the activity (/fragment) where they are used.
So, all strings, styles, ids used in the context of "ViewPost" should start be "#id/view_post_heading" (for a textview for example), "#style/view_post_heading_style", "#string/view_post_greeting".
This will optimize autocomplete, organization, avoid name colision, etc.
Base Classes
I think you'll want to use base classes for pretty much everything you do: Adapters, Activities, Fragments, Services, etc. These might be useful at least for debugging purposes so you know which events are happening in all your activity.
General
I never use anonymous classes - these are ugly and will drive your attention away when you are trying to read the code
Sometimes I prefer to use inner classes (compared to create a dedicated class) - if a class is not going to be used anywhere else (and it's small) I think this is very handy.
Think about your logging system from the beginning - you can use android's logging system but make a good use of it!
I think it would be more helpful to explain MVVM in android through an example. The complete article together with the GitHub repo info is here for more info.
Let’s suppose the same benchmark movie app example introduced in the first part of this series. User enters a search term for a movie and presses the ‘FIND’ button, based on which the app searches for the list of movies including that search term and shows them. Clicking on each movie on the list then shows its details.
I will now explain how this app is implemented in MVVM followed by the complete android app, which is available on my GitHub page.
When the user clicks on the ‘FIND’ button on the View, a method is called from the ViewModel with the search term as its argument:
main_activity_button.setOnClickListener({
showProgressBar()
mMainViewModel.findAddress(main_activity_editText.text.toString())
})
The ViewModel then calls the findAddress method from the Model to search for the movie name:
fun findAddress(address: String) {
val disposable: Disposable = mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>() {
override fun onSuccess(t: List<MainModel.ResultEntity>) {
entityList = t
resultListObservable.onNext(fetchItemTextFrom(t))
}
override fun onError(e: Throwable) {
resultListErrorObservable.onNext(e as HttpException)
}
})
compositeDisposable.add(disposable)
}
When the response comes from the Model, the onSuccess method of the RxJava observer carries the successful result, but as the ViewModel is View agnostic, it does not have or use any View instance to pass the result for showing. It instead triggers an event in the resultListObservable by calling resultListObservable.onNext(fetchItemTextFrom(t)) , which is observed by the View:
mMainViewModel.resultListObservable.subscribe({
hideProgressBar()
updateMovieList(it)
})
So the observable plays a mediator role between the View and ViewModel:
ViewModel triggers an event in its observable
View updates the UI by subscribing to ViewModel’s observable
Here’s the full code for the View. In this example, View is an Activity class, but Fragment can also be equally used:
class MainActivity : AppCompatActivity() {
private lateinit var mMainViewModel: MainViewModel
private lateinit var addressAdapter: AddressAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mMainViewModel = MainViewModel(MainModel())
loadView()
respondToClicks()
listenToObservables()
}
private fun listenToObservables() {
mMainViewModel.itemObservable.subscribe(Consumer { goToDetailActivity(it) })
mMainViewModel.resultListObservable.subscribe(Consumer {
hideProgressBar()
updateMovieList(it)
})
mMainViewModel.resultListErrorObservable.subscribe(Consumer {
hideProgressBar()
showErrorMessage(it.message())
})
}
private fun loadView() {
setContentView(R.layout.activity_main)
addressAdapter = AddressAdapter()
main_activity_recyclerView.adapter = addressAdapter
}
private fun respondToClicks() {
main_activity_button.setOnClickListener({
showProgressBar()
mMainViewModel.findAddress(main_activity_editText.text.toString())
})
addressAdapter setItemClickMethod {
mMainViewModel.doOnItemClick(it)
}
}
fun showProgressBar() {
main_activity_progress_bar.visibility = View.VISIBLE
}
fun hideProgressBar() {
main_activity_progress_bar.visibility = View.GONE
}
fun showErrorMessage(errorMsg: String) {
Toast.makeText(this, "Error retrieving data: $errorMsg", Toast.LENGTH_SHORT).show()
}
override fun onStop() {
super.onStop()
mMainViewModel.cancelNetworkConnections()
}
fun updateMovieList(t: List<String>) {
addressAdapter.updateList(t)
addressAdapter.notifyDataSetChanged()
}
fun goToDetailActivity(item: MainModel.ResultEntity) {
var bundle = Bundle()
bundle.putString(DetailActivity.Constants.RATING, item.rating)
bundle.putString(DetailActivity.Constants.TITLE, item.title)
bundle.putString(DetailActivity.Constants.YEAR, item.year)
bundle.putString(DetailActivity.Constants.DATE, item.date)
var intent = Intent(this, DetailActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
class AddressAdapter : RecyclerView.Adapter<AddressAdapter.Holder>() {
var mList: List<String> = arrayListOf()
private lateinit var mOnClick: (position: Int) -> Unit
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
val view = LayoutInflater.from(parent!!.context).inflate(R.layout.item, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.item_textView.text = mList[position]
holder.itemView.setOnClickListener { mOnClick(position) }
}
override fun getItemCount(): Int {
return mList.size
}
infix fun setItemClickMethod(onClick: (position: Int) -> Unit) {
this.mOnClick = onClick
}
fun updateList(list: List<String>) {
mList = list
}
class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView)
}
}
Here is the ViewModel:
class MainViewModel() {
lateinit var resultListObservable: PublishSubject<List<String>>
lateinit var resultListErrorObservable: PublishSubject<HttpException>
lateinit var itemObservable: PublishSubject<MainModel.ResultEntity>
private lateinit var entityList: List<MainModel.ResultEntity>
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
private lateinit var mainModel: MainModel
private val schedulersWrapper = SchedulersWrapper()
constructor(mMainModel: MainModel) : this() {
mainModel = mMainModel
resultListObservable = PublishSubject.create()
resultListErrorObservable = PublishSubject.create()
itemObservable = PublishSubject.create()
}
fun findAddress(address: String) {
val disposable: Disposable = mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>() {
override fun onSuccess(t: List<MainModel.ResultEntity>) {
entityList = t
resultListObservable.onNext(fetchItemTextFrom(t))
}
override fun onError(e: Throwable) {
resultListErrorObservable.onNext(e as HttpException)
}
})
compositeDisposable.add(disposable)
}
fun cancelNetworkConnections() {
compositeDisposable.clear()
}
private fun fetchItemTextFrom(it: List<MainModel.ResultEntity>): ArrayList<String> {
val li = arrayListOf<String>()
for (resultEntity in it) {
li.add("${resultEntity.year}: ${resultEntity.title}")
}
return li
}
fun doOnItemClick(position: Int) {
itemObservable.onNext(entityList[position])
}
}
and finally the Model:
class MainModel {
private var mRetrofit: Retrofit? = null
fun fetchAddress(address: String): Single<List<MainModel.ResultEntity>>? {
return getRetrofit()?.create(MainModel.AddressService::class.java)?.fetchLocationFromServer(address)
}
private fun getRetrofit(): Retrofit? {
if (mRetrofit == null) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build()
mRetrofit = Retrofit.Builder().baseUrl("http://bechdeltest.com/api/v1/").addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).client(client).build()
}
return mRetrofit
}
class ResultEntity(val title: String, val rating: String, val date: String, val year: String)
interface AddressService {
#GET("getMoviesByTitle")
fun fetchLocationFromServer(#Query("title") title: String): Single<List<ResultEntity>>
}
}
Full article here