Retrofit for API Calls with Hilt Dependency Injection

Anumshafiq
4 min readDec 19, 2023

--

In the preceding article, we successfully incorporated Room accessing and storing local data using a Local repository. Now, we’ll focus on making API calls using Retrofit to establish a Remote Repository. Using a combination of Local and Remote Data Sources in your application is possible based on your needs.

In many applications, accessing data from a server via API calls is a necessity. There exist various approaches for this purpose, but we’ll adopt Retrofit and Hilt for this tutorial. Retrofit is a widely embraced HTTP client library on Android, simplifying interactions with RESTful APIs. Conversely, Hilt serves as a dependency injection library for Android, aiding in the management and injection of dependencies within your application.

Our objective will be to utilize the TMDB API as the Remote data source. Obtaining an Authentication API key is straightforward; you can easily acquire it from https://developer.themoviedb.org/reference/intro/authentication#api-key-quick-start. Remember to include this key in your project as ACCESS_TOKEN within the Constant class.

Let’s begin the game, including the basic libraries in the project.

//Hilt dependencies. Use latest versions for better experience
implementation("com.google.dagger:hilt-android:2.48.1")
kapt("com.google.dagger:hilt-compiler:2.48.1")
//Retrofit Dependecies.
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

Create an Application class and annotate your Application class @HiltAndroidApp to enable Hilt in your application. Then create an interface that defines your API endpoints. Here, we are only defining two of them. In functionfetchMovies() API now_playing has been called and for functiongetDetail API movie_id has been called.

interface MyApiService {
@GET("now_playing")
suspend fun fetchMovies(): Response<MovieResponse>

@GET("{movie_id}")
suspend fun getDetail(
@Path("movie_id") movieId: String,
@Query("language") language: String = "en-US"
): Response<MovieDetails>
}

Write your AppModule class that will be used by the Hilt that provides various dependencies required for network-related operations, like Retrofit, OkHttp, Gson, and an authentication interceptor.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

@Provides
fun provideBaseUrl(): String {
return Constant.BASE_URL
}

@Provides
fun provideGson(): Gson {
return GsonBuilder()
.create()
}

@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(
provideAuthInterceptor()
)
.build()
}

@Provides
fun provideAuthInterceptor(): AuthInterceptor {
return AuthInterceptor(Constant.ACCESS_TOKEN)
}

@Provides
fun provideRetrofit(
baseUrl: String,
gson: Gson,
okHttpClient: OkHttpClient
): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}

@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): MyApiService =
retrofit.create(MyApiService::class.java)

}

In the above code provideBaseUrl() Provides the base URL, provideGson()Provides a Gson instance for JSON serialization/deserialization , provideOkHttpClient()Provides an OkHttpClient instance and adds an AuthInterceptor to it for token authentication.provideAuthInterceptor() Provides an instance of AuthInterceptor with the access token provided via Constant.ACCESS_TOKEN.provideRetrofit()Constructs and provides the Retrofit instance using the base URL, Gson instance, and OkHttpClient.provideApiService() Provides an instance of the MyApiService interface using Retrofit. With authInterceptor code below:

class AuthInterceptor(private val authToken: String) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()

val requestBuilder = originalRequest.newBuilder()
.addHeader("accept", "application/json")
.addHeader("Authorization", "Bearer $authToken")

val request = requestBuilder.build()
return chain.proceed(request)
}
}

Now define MovieRepository class which seems to encapsulate the interaction with apiService (an instance of MyApiService) to fetch movies and retrieve details of a particular movie based on its ID. The structure is typical of a repository pattern where data retrieval from an API is abstracted from the rest of the application. (Single Source Of Truth)

class MovieRepository @Inject constructor(
private val apiService: MyApiService
) {
suspend fun fetchMovies() = apiService.fetchMovies()
suspend fun getDetail(movieId: String) = apiService.getDetail(movieId)
}

Finally, theMovieViewModel class uses the MovieRepository to interact with the data layer and manage the UI-related data using LiveData, where Hilt provides the movieRepository instance through@Inject . The ViewModel fetches a list of movies and retrieves details of a specific movie based on its ID, returning the List<Movie> and MovieDetails as the Response Objects.

MovieViewModel @Inject constructor(
private val movieRepository: MovieRepository
) : ViewModel() {

private val _moviesLiveData = MutableLiveData<List<Movie>>()
val moviesLiveData: LiveData<List<Movie>> get() = _moviesLiveData

private val _movieDetailLiveData = MutableLiveData<MovieDetails>()
val detailLiveData: LiveData<MovieDetails> get() = _movieDetailLiveData


init {
fetchMovies()
}

private fun fetchMovies() {
viewModelScope.launch {
try {
val newItem = withContext(Dispatchers.IO) {
movieRepository.fetchMovies()
}
_moviesLiveData.postValue(newItem.body()?.results)
Log.e("element", newItem.toString())
} catch (e: Exception) {
// Handle exceptions, if any
Log.e("getDetail", "Error fetching details: ${e.message}")
}
}
}

fun getDetail(movieId: String) {
viewModelScope.launch {
try {
val response = movieRepository.getDetail(movieId)
_movieDetailLiveData.value = response.body()

} catch (e: Exception) {

}
}
}

As we are using TMDB API so baseurl and thumbnailUrlwill look like this including ACCESS_TOKEN in the Constant file of the application. For further details on the API, you can check https://developer.themoviedb.org/docs.

object Constant {
const val BASE_URL = "https://api.themoviedb.org/3/movie/"
const val THUMBNAIL_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w154/"
const val POSTER_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/original"
const val ACCESS_TOKEN = "Your Access Token for the TMDB API"
}

MovieResponseclass provides data classes representing the structure of movie-related data received from an API response. You can use the attributes as required and write your MovieDetail class for Movie Detail Response.

data class MovieResponse(
val dates: DateRange,
val page: Int,
val results: List<Movie>,
val total_pages: Int,
val total_results: Int
)

data class DateRange(
val maximum: String,
val minimum: String
)

data class Movie(
val adult: Boolean,
val backdrop_path: String?,
val genre_ids: List<Int>,
val id: Int,
val original_language: String,
val original_title: String,
val overview: String,
val popularity: Double,
val poster_path: String?,
val release_date: String,
val title: String,
val video: Boolean,
val vote_average: Double,
val vote_count: Int
)

At the end, we have the UI Layer where we will implement the fragment or an Activity depending upon the requirement of our application. Annotate your Main Activity Class as @AndroidEntryPoint or Fragment in which you are using the ViewModel . Get the instance of the viewModel through activityViewModel or hiltViewModel depending on the use of the UI.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//....
private val movie: MovieViewModel by activityViewModels()

You can continue the UI layer from this article https://medium.com/@anumshafiq89/creating-a-vertical-list-with-jetpack-compose-cb42e1d021cf. And you have successfully implemented the Retrofit using Hilt .

In the article, I demonstrate the utilization of the TMDB API, but the core principles can be applied to any API server by modifying the function names and endpoints. Any feedback on this approach is greatly appreciated. Enjoy coding!

--

--

Anumshafiq

Android App. Developer with over 8 years of experience. Eager to learn and adopt new technologies. Open to queries and suggestions in the field of development.