Implementation
Loading catalogs#
This guide covers how to expose catalogs (home-page sections) using the capability-first CatalogProviderApi.
It assumes you’ve read the paging container section in the search guide: PaginatedResponse.
Overview#
Catalogs represent curated collections of media content shown on the home screen.
To support catalogs, expose a CatalogProviderApi from your ProviderPlugin:
class TestProviderPlugin : ProviderPlugin() {
private val client by lazy { OkHttpClient() }
private val catalogApi by lazy {
TestCatalogApi(
client = client,
providerId = id,
tmdbApiKey = settings.getString("tmdb_api_key", null) ?: "",
)
}
override fun getCatalogApi(context: Context): CatalogProviderApi = catalogApi
}CatalogProviderApi has two key pieces:
getCatalogs(): returns aList<ProviderCatalog>asynchronouslygetCatalogItems(...): loads items for a specific catalog + page
class TestCatalogApi(
private val client: OkHttpClient,
private val providerId: String,
private val tmdbApiKey: String,
) : CatalogProviderApi {
override suspend fun getCatalogs(): List<ProviderCatalog> = emptyList()
override suspend fun getCatalogItems(
catalog: ProviderCatalog,
page: Int,
): PaginatedResponse<FilmSearchItem> {
// ...
}
}ProviderCatalog#
A ProviderCatalog describes a catalog entry.
| Property | Description |
|---|---|
name | Display name shown in the host UI. |
url | Endpoint (or resolvable URL) used to load items for this catalog. |
mediaType | Whether this catalog represents movies or TV shows (FilmType). |
canPaginate | Whether the host should show “Load more”. |
image | Optional image/icon for the catalog. |
providerId | The provider id that owns this catalog. |
description | Optional description shown in the host UI. |
headers | Optional request headers to use when calling url. |
Example using TMDB Discover API#
In this section, TMDB’s discover endpoints are used as an example.
Define DTO classes#
Reuse the DTOs (and mapping) from the Searching media guide.
Define your catalogs#
Convert endpoints into ProviderCatalog instances. Here, we use TMDB’s discover/movie and discover/tv.
private val trendingMovies = ProviderCatalog(
name = "Trending Movies",
url = "https://api.themoviedb.org/3/discover/movie",
mediaType = FilmType.MOVIE,
canPaginate = true,
providerId = providerId,
description = "Popular movies right now",
)
private val popularTvShows = ProviderCatalog(
name = "Popular TV Shows",
url = "https://api.themoviedb.org/3/discover/tv",
mediaType = FilmType.TV_SHOW,
canPaginate = true,
image = "https://api.themoviedb.org/sample_icon.png",
providerId = providerId,
)
override suspend fun getCatalogs(): List<ProviderCatalog> = listOf(
trendingMovies,
popularTvShows,
)Implement getCatalogItems(...)#
Call the catalog URL, add paging/query params, parse the response, and map items to FilmSearchItem.
private fun buildCatalogUrl(
baseUrl: String,
page: Int,
apiKey: String,
): String {
return "$baseUrl?api_key=$apiKey&page=$page"
}
override suspend fun getCatalogItems(
catalog: ProviderCatalog,
page: Int,
): PaginatedResponse<FilmSearchItem> {
val dto = client
.request(url = buildCatalogUrl(catalog.url, page, tmdbApiKey))
.execute()
.fromJson<TmdbSearchResponseDto>()
val forcedType = catalog.mediaType
val results = dto.results
.mapNotNull { it.toFilmSearchItemOrNull(providerId = providerId, forcedType = forcedType) }
return PaginatedResponse(
page = dto.page,
results = results,
hasNextPage = dto.page < dto.totalPages,
totalPages = dto.totalPages,
)
}If your catalog API does not return total_pages, you can set totalPages = 0 and derive
hasNextPage based on the returned list size.
Final output#
class TestCatalogApi(
private val client: OkHttpClient,
private val providerId: String,
private val tmdbApiKey: String,
) : CatalogProviderApi {
private val trendingMovies = ProviderCatalog(
name = "Trending Movies",
url = "https://api.themoviedb.org/3/discover/movie",
mediaType = FilmType.MOVIE,
canPaginate = true,
providerId = providerId,
)
private val popularTvShows = ProviderCatalog(
name = "Popular TV Shows",
url = "https://api.themoviedb.org/3/discover/tv",
mediaType = FilmType.TV_SHOW,
canPaginate = true,
providerId = providerId,
)
override suspend fun getCatalogs(): List<ProviderCatalog> = listOf(
trendingMovies,
popularTvShows,
)
override suspend fun getCatalogItems(
catalog: ProviderCatalog,
page: Int,
): PaginatedResponse<FilmSearchItem> {
val dto = client
.request(url = buildCatalogUrl(catalog.url, page, tmdbApiKey))
.execute()
.fromJson<TmdbSearchResponseDto>()
val forcedType = catalog.mediaType
val results = dto.results
.mapNotNull { it.toFilmSearchItemOrNull(providerId = providerId, forcedType = forcedType) }
return PaginatedResponse(
page = dto.page,
results = results,
hasNextPage = dto.page < dto.totalPages,
totalPages = dto.totalPages,
)
}
}