Starting point
This article is based on a simple app that loads a list of movies and tv shows from TheMovieDb. In the series of articles (yes, I am planning to do more), we will try to upgrade it to the next level.
Currently, our calls look like this
Not so bad, but there are some issues here:
- There is no error handling, we need to remember to catch it on other layers
- An API key is manually added to every url, the same goes for language. This is not a reusable solution. What would happen if we used JWT (JSON Web Tokens) and need to handle refreshing in every call? A total mess.
- So, what do you recommend?
- I’m glad you asked. As always, when we want to create something universal, we should create an abstraction. So, let’s do that!
Create abstract Api class
We need to create a new abstract class. In our case, it will be called Api. It will have a get method that returns an HTTP Get response for a given url. From the previous sentence, it seems, that we did not change much, and that is correct. Well.. almost. A response is not the same for each call so we need a universal type of response. One of the possibilities could be the use of dynamic
type, but that is not the way to go. The better solution is to create another abstract class Response
that can be successful or not. After applying those changes we will obtain:
Ok, that is a step forward, but there are a few more. When we would apply those changes to our initial call, we would obtain an error as the return type is not correct. Even after fixing it, one more error would be left in our response mapper, as the current class we returned wasn’t implementing a Response
. We need to make some changes to fix the issue.
Upgrading return types
As of now, we returned directly the desired class, but it is not a Response type. The simple solution would be to implement Response or even better SuccessfulResponse. But we can go a step further and use Freezed library. That will generate us a fromJson constructor and other helpful methods. (Remember, this step is not required, and you can skip to the next part.)
Ughh… This looks like a highly dens chunker… Aren’t all response classes with JSON mappers looking ugly?
Ok, we can do this, let’s break it down into smaller pieces, so we can understand what is going on.
Starting from the top we have ContentListResponse
which implements SuccessfulResponse
. Then we have the first constructor which tells freezed what fields the generated class should have. There we also specify that it should implement SuccessfulResponse
. We can also notice that the first argument of the constructor has ContentResponseConverter()
annotation. We will discuss it in a moment. Then there is a named constructor fromJson
which is self-explanatory.
Next up is ContentResponse
class. It has 3 constructors. The first one is for movies and the second one for tv shows, as our app displays both. They have almost the same fields in JSON. The difference can be seen in JsonKey
for firstAirDate and title. That is the single reason we had to create two separate constructors. Fortunately, when operating on the ContentResponse
object, we will have access to all the fields, as the names of the parameters in constructors are the same. The third one is obvious as before.
Now we can see where that ContentResponseConverter()
annotation comes from. We had to create a JsonConverter so we can correctly choose which constructor should be used for our ContentResponse. To determine that, we are checking if the given JSON contains a key name
, which is characteristic for the tv show response.
Api call with new response type
Having this out of the way, we can come back to our initial API call, perform necessary changes, and as the result, we get a simple call.
Hurray! We did it!
Wait… It looks almost the same, we still did not handle errors, and the API key is added manually… Don’t worry, the hard part is done. Now, the straightforward process is left. We will finish in no time.
Handling authorization and errors
Our movie app is authorized by adding api_key
parameter into uri
query. We can easily do that by obtaining the current parameters from uri
which are represented as a map and adding api_key
there. As for handling errors, we need to check the response’s status code and http.get()
exceptions.
After those changes, our call will look pretty similar to the previous one. The main difference is the removal of api_key
from the query. We can do the same with language
, but as always, it depends on our requirements.
Changes in repository
The last change in our code is left. Handling the response in repository.
Yes, I know, now it is a bit longer, but you can easily handle different types of responses or errors without raising an unhandled exception. In our case, we want to return an empty list when loading from API fails.
Every project is different, and a lot of them would need other HTTP methods other than GET. Now we know the idea, so adding others is pretty easy. The only thing we need to change is the http
method.
What I have shown here is not the final form. In the next article, I will show a better way to handle the response and show customized error messages in the UI to the user.
This is my first article, I hope you enjoyed it. If you have any ideas what I can improve, let me know in the comments.
Link to the app on which this article is based (branch: better_api_calls):
Part 2