Retrofit + Kotlin Coroutines = Bye-bye, RxJava!
Kotlin Coroutines simplify asynchronous programming, leaving all the complications inside the libraries.
I created a simple project: Retrofit Service, 1 Activity, 1 Adapter, 1 Model.
Here’s what I have in the app level of build.gradle: RecyclerView, Coroutines, Retrofit:
dependencies {
// ...
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.26.1'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0'
}
kotlin {
experimental {
coroutines "enable"
}
}
androidExtensions {
experimental = true
}
Model: (I will use the old good JSONPlaceholder):
data class Post(
val userId: Long,
val id: Long,
val title: String,
val body: String
): Serializable
Adapter and holder (by the way, an interesting solution with LayoutContainer that allows you to import View elements through Kotlin Extensions, for this, by the way, you need the androidExtensions -> experimental = true block in build.gradle)
class MainAdapter(var items: List<Post>): RecyclerView.Adapter<MainAdapter.PostHolder>() {
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PostHolder(LayoutInflater.from(parent.context).inflate(R.layout.post_item, parent, false))
override fun onBindViewHolder(holder: PostHolder, position: Int) { holder.bind(items[position]) }
inner class PostHolder(override val containerView: View): RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(item: Post) {
postId.text = item.id.toString()
postTitle.text = item.title
postBody.text = item.body
}
}
}
Retrofit API interface:
interface JsonPlaceholderApi {
@GET("/posts")
fun getPosts(): Deferred<Response<List<Post>>>
companion object {
private const val BASE_URL = "https://jsonplaceholder.typicode.com"
fun getApi(): JsonPlaceholderApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
.create(JsonPlaceholderApi::class.java)
}
And, finally, Activity and layouts:
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = MainAdapter(listOf())
recycler.adapter = adapter
GlobalScope.launch(Dispatchers.Main) {
progress.visibility = View.VISIBLE
val postsRequest = JsonPlaceholderApi.getApi().getPosts()
val postsResponse = postsRequest.await()
progress.visibility = View.GONE
if (postsResponse.isSuccessful) {
adapter.items = postsResponse.body() ?: listOf()
adapter.notifyDataSetChanged()
} else {
Toast.makeText(this@MainActivity, "Error ${postsResponse.code()}", Toast.LENGTH_SHORT).show()
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<ProgressBar
android:id="@+id/progress"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="@layout/post_item"
/>
</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
>
<TextView
android:id="@+id/postId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@android:color/darker_gray"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="id"
/>
<TextView
android:id="@+id/postTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/postId"
android:layout_marginEnd="10dp"
tools:text="title"
/>
<TextView
android:id="@+id/postBody"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/postTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="5dp"
tools:text="body"
/>
</android.support.constraint.ConstraintLayout>
Enjoy! All code (with minor improvements) as always on GitHub.