# OkHttp+MVVM=Retrofit

People have been asking the question "Retrofit vs OkHttp" for years. Most of the answers would be a feature comparison, pros and cons, then they'll tell you Retrofit saves you so much time.

Now it is time for a different answer.

I'm gonna convince you Retrofit is better than OkHttp when you follow MVVM.

Suppose you are writing a post detail screen in a blog app. All you need to do is call 1 api and display the data.

How would you write the same feature with different ideas applied.

# God Activity Architecture

Let's start basic, remember the good old days when we write everything in `Activity`?
Typically, these are what you do

- create a `getPost(id: String)` function accept a post id as a parameter
- concat String to create a url
- make a network request directly with [OkHttp](https://square.github.io/okhttp/)
- onResponse callback, parse response and display the data
- onFailure callback, show some sort of failure message

```kotlin
class PostDetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_post_details)
        getPost(intent.getStringExtra("POST_ID")!!)
    }

    fun getPost(id: String) {
        val okhttp = OkHttpClient.Builder().build()
        val request = Request.Builder().get().url("https://somewh.ere/api/v1/posts/$id").build()
        okhttp.newCall(request).enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                val body = response.body!!.string()
                val post = Gson().fromJson(body, Post::class.java)
                setView(post)
            }

            override fun onFailure(call: Call, e: IOException) {
                Toast.makeText(this@PostDetailActivity, e.message, Toast.LENGTH_SHORT).show()
            }
        })
    }

    fun setView(post: Post) {
        //TODO
    }
}
```

# MVVM

Now we apply **Separation of Concern** or else you get frowned upon.

- M-Model, `PostApi`
  - put implementation details of network call and deserialization here
  - wrap the asynchronous callback with coroutine because it's 2022
  - or you might go with old school callback and declare the function
    like `fun getPost(id: String, callback: Callback<Post>)`, doesn't really matter
- VM-ViewModel, `PostDetailViewModel`
  - accept `PostApi` as a constructor parameter, easy DI, testable
  - holds the data
- V-View, `MvvmPostDetailActivity`
  - UI manipulation
  - observe data from `PostDetailViewModel`
  - an instance of `PostDetailViewModel` should be obtained through a DI framework but let's leave it like this for simplicity

```kotlin
class PostApi {
    suspend fun getPost(id: String): Post = suspendCoroutine { continuation ->
        val okhttp = OkHttpClient.Builder().build()
        val request = Request.Builder().get().url("https://somewh.ere/api/v1/posts/$id").build()
        okhttp.newCall(request).enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                val body = response.body!!.string()
                val post = Gson().fromJson(body, Post::class.java)
                continuation.resume(post)
            }

            override fun onFailure(call: Call, e: IOException) {
                continuation.resumeWithException(e)
            }
        })
    }
}

class PostDetailViewModel(val postApi: PostApi) : ViewModel() {
    val postLiveData: MutableLiveData<Post> = MutableLiveData()
    val errorLiveData: MutableLiveData<String> = MutableLiveData()
    fun getPost(id: String) {
        viewModelScope.launch {
            try {
                val p = postApi.getPost(id)
                postLiveData.value = p
            } catch (e: Exception) {
                errorLiveData.value = e.message
            }
        }
    }
}

class MvvmPostDetailActivity : AppCompatActivity() {

    val viewModel: PostDetailViewModel = PostDetailViewModel(PostApi())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_post_details)
        viewModel.postLiveData.observe(this) { post: Post ->
            setView(post)
        }
        viewModel.errorLiveData.observe(this) { message: String ->
            Toast.makeText(this@MvvmPostDetailActivity, message, Toast.LENGTH_SHORT).show()
        }
        viewModel.getPost(intent.getStringExtra("POST_ID")!!)
    }

}
```

# Code Against Interfaces, Not Implementations

I have no idea why this principle(?) exits, isn't that just how interface
works? Anyway, I'm sure you have been in a situation where you develop frontend/mobile app but the API is not ready, and you have to make due with mock data.

- convert `PostApi` to an interface with `suspend fun getPost(id: String): Post`
- `RealPostApi` implements `PostApi.getPost()` and call the actual API
- `MockPostApi` implements `PostApi.getPost()` but return mock data of your choice
- you can now swap between real data and mock data with just one line of code (where you create ViewModel)
- if you have proper DI setup, no code changes in the Activity needed (the change will be in your DI setup instead)

```kotlin
interface PostApi {
    suspend fun getPost(id: String): Post
}

class RealPostApi : PostApi {
    override suspend fun getPost(id: String): Post = suspendCoroutine { continuation ->
        val okhttp = OkHttpClient.Builder().build()
        val request = Request.Builder().get().url("https://somewh.ere/api/v1/posts/$id").build()
        okhttp.newCall(request).enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                val body = response.body!!.string()
                val post = Gson().fromJson(body, Post::class.java)
                continuation.resume(post)
            }

            override fun onFailure(call: Call, e: IOException) {
                continuation.resumeWithException(e)
            }
        })
    }
}

class MockPostApi : PostApi {
    override suspend fun getPost(id: String): Post {
        delay(2000)
        if (id == "404") {
            throw Exception("A post with id = $id does not exist")
        } else {
            return Post(id, "Peace Treaty", "Ceres Fauna")
        }
    }
}

class MvvmPostDetailActivity : AppCompatActivity() {

    val viewModel: PostDetailViewModel = PostDetailViewModel(MockPostApi())
//    val viewModel: PostDetailViewModel = PostDetailViewModel(RealPostApi())

}
```

# Retrofit

Man, hand-written http request and deserialization are such tedious tasks, and you have to do this for every single api call.

But, wait a minute... this looks familiar somehow.

Right, we can just annotate our `PostApi` interface and have a fully functional "Real"`PostApi` instance created by `Retrofit` without ever have to write `RealPostApi` ourselves!

```kotlin
interface PostApi {
    @GET("https://somewh.ere/api/v1/posts/{id}")
    suspend fun getPost(@Path("id") id: String): Post
}

class MvvmPostDetailActivity : AppCompatActivity() {

    val viewModel: PostDetailViewModel =
            PostDetailViewModel(Retrofit.Builder().build().create(PostApi::class.java))
    //    val viewModel: PostDetailViewModel = PostDetailViewModel(MockPostApi())

}
```

Since `PostDetailViewModel` still depends on an interface `PostApi`, the flexibility of swap the mock implementation is still there, still easy to test. And if later on you decide to hand-write `RealPostApi` yourself, you can still do that without changing any line of code inside `PostDetailViewModel`.

Yeah, the Retrofit creation code is kind of ugly in the Activity, so you should have a proper DI setup.

# Conclusion

Even you started out using OkHttp, when you adopt MVx architecture, write testable code and use interfaces wisely, Retrofit can replace your hand-written API call implementation. I guess this is what it means by *Retrofit turns your HTTP API into a Java interface* as written in the first line on the [website.](https://square.github.io/retrofit/)


