This android project gives an example about how to use shared modules for two platforms: mobile and tv. And also to make a coexisting project with xml and compose for the UI.
This project shows an app fetching movies from an API and show them in a list.
Mobile | TV |
---|---|
And now also list of radio channels and list of Popular YouTube Channels.
Streaming Demo Video (YouTube) | Streaming Demo Radio |
---|---|
- Multi Module Clean Architecture (Android TV)
The project has the following modules:
- app: main module of the project
- core: module for shared libraries/resources not related to UI
- core-ui: module for shared libraries/resources related to UI
- movie: module for the feature movie
- movie-data
- movie-domain
- movie-ui
- movie-ui-mobile
- movie-ui-tv
- navigation
- preferences
- streaming
- streaming-data
- streaming-domain
- streaming-ui
Now we need to define these variables in local.properties
:
moviedb.key=
youtube.key=
Using The Movie Database API for this project.
URL BASE: https://api.themoviedb.org/3/
GET movie/now_playing
Parameter | Type | Description |
---|---|---|
language |
String |
Required |
page |
Int |
Required |
This module encapsulates all features related to "movie", in case after I need to add another one. And it
- movie-data: works as the data layer for the feature "movie". it manages data storage, fetching data and network operations.
- movie-domain: contains the bussiness logic and rules of the app (use cases). This module connects the UI and data modules (layers).
- movie-ui: contains the presentation and interaction with the user
- movie-ui-mobile: uses XML for design and implements views related to mobile platform
- movie-ui-tv: uses Jetpack Compose for design and implements views related to TV platform
The project is using DaggerHilt for dependency injection, every module provides different dependencies.
Provides these classes:
- MovieDataModule
- OAuthInterceptor
- MovieApi
- NowPlayingMovieListResponse
- MovieRepositoryImpl
- MovieDtoMapper
Provides these classes:
- MovieDomainModule
- Movie
- MovieRepository
- GetNowPlayingMovies
Provides these classes (using Jetpack Compose):
- MovieListItem
- Movie
- MovieListScreen
- MovieListState
- MovieListViewModel
Provides these classes (using XML):
- MoviePresenter
- ErrorFragment
- MovieListFragment
- SpinnerFragment
- MovieListEvent
- MovieListViewModel
This module uses the previous modules and configures two flavors to separate one version for mobile and one for TV.
Flavor | Description |
---|---|
mobile |
Flavor for mobile platform |
tv |
Flavor for tv platform |
streaming |
Flavor for tv streming: radio and video streaming |
flavorDimensions("platform")
productFlavors {
create("mobile") {
dimension = "platform"
}
create("tv") {
dimension = "platform"
}
create("streaming") {
dimension = "platform"
applicationIdSuffix = ".streaming"
}
}
sourceSets {
getByName("mobile") {
manifest.srcFile("src/mobile/AndroidManifest.xml")
}
getByName("tv") {
manifest.srcFile("src/tv/AndroidManifest.xml")
}
getByName("streaming") {
manifest.srcFile("src/streaming/AndroidManifest.xml")
}
}
Here the only difference is that TV manifest use Leanback Launcher:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
This project has two base gradle files to implement in other modules:
-
Base Module Gradle:
Includes all needed dependencies for module that doesn't use or required UI or android libraries
base-module.gradle
-
Compose Module Gradle:
Includes all needed dependencies for module that use Jetpack Compose
compose-module.gradle
-
No Compose Module Gradle:
Includes all needed dependencies for module that doesn't use Jetpack Compose, and use XML for designing views. For this project, includes Leanback for Android TV libraries.
no-compose-module.gradle
import com.devsu.buildsrc.Modules
apply {
from("$rootDir/movie-domain-module.gradle")
}
dependencies {
"implementation"(project(Modules.core))
"implementation"(project(Modules.coreUi))
}
import com.devsu.buildsrc.Modules
import com.devsu.buildsrc.Retrofit
apply {
from("$rootDir/movie-data-module.gradle")
}
dependencies {
"implementation"(project(Modules.core))
"implementation"(project(Modules.coreUi))
"implementation"(project(Modules.movieDomain))
"implementation"(platform(Retrofit.okHttpBmo))
"implementation"(Retrofit.okHttp)
"implementation"(Retrofit.okHttpLoggingInterceptor)
"implementation"(Retrofit.retrofit)
"implementation"(Retrofit.gsonConverter)
}
import com.devsu.buildsrc.Modules
import com.devsu.buildsrc.Coil
import com.devsu.buildsrc.DaggerHilt
apply {
from("$rootDir/movie-ui-mobile-module.gradle")
}
dependencies {
"implementation"(project(Modules.core))
"implementation"(project(Modules.coreUi))
"implementation"(project(Modules.movieDomain))
"implementation"(Coil.coilCompose)
}
import com.devsu.buildsrc.Modules
import com.devsu.buildsrc.Coil
apply {
from("$rootDir/movie-ui-tv-module.gradle")
}
dependencies {
"implementation"(project(Modules.core))
"implementation"(project(Modules.coreUi))
"implementation"(project(Modules.movieDomain))
"implementation"(Coil.coilKotlin)
}
This module tries to simulate how will be implementing a video and radio streaming using YouTube API and also a free API called Radio Browser. And also the main goal is use Jetpack Compose for Android TV.
All views in this module are using Jetpack Compose for Android TV.
For this case, we will have the channels id list in the app, and we will use it to get the video ids and show the video. You need to get your API in Google Cloud Console. Find more information here.
URL BASE: https://www.googleapis.com/youtube/v3/
GET search
Parameter | Type | Description |
---|---|---|
key |
String |
Required |
channelId |
String |
Required |
part |
String |
|
order |
String |
|
order |
String |
|
maxResults |
String |
This is a free API we are using to get list of radios by gender, country or most populars. Find more information here.
URL BASE: https://de1.api.radio-browser.info/
GET json/stations/search
Parameter | Type |
---|---|
limit |
Int |
offset |
Int |
order |
String |
reverse |
Boolean |
According to the documentation, we can't use directly a Youtube Player because now it's deprecated, so now we need to handle it as a webview using iFrame. So to make it easier for this demo, I'm using android-youtube-player to handle all the actions and loading the videos. This library is using kotlin, but we are going to use the view in Compose.
- Movie Domain
- GetNowPlayingMoviesTest
- Movie Data
- MovieDtoMapperTest
- MovieApiTest
I did a previous example using multi modules but only for mobile using XML and compose, so I wanted to take this as a challenge and use another platform like Android TV.
Just for fun. Jetpack Compose becomes more stable and it's easier and need less code to design views.
Android Studio Iguana | 2023.2.1 Patch 1
Build #AI-232.10300.40.2321.11567975, built on March 13, 2024
Runtime version: 17.0.9+0-17.0.9b1087.7-11185874 aarch64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
macOS 14.5
GC: G1 Young Generation, G1 Old Generation
Memory: 2048M
Cores: 10
Metal Rendering is ON
Registry:
ide.experimental.ui=true
Non-Bundled Plugins:
org.sonarlint.idea (8.4.0.73538)
- Jordan Rojas (@nowjordanhappy)
MIT License
Copyright (c) 2024 Jordan R. A.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.