Projeto desenvolvido com o intuito de ajudar na criação/setup de novos projetos Android modularizado, diminuindo consideravelmente o tempo de configuração do gradle e buildSrc.
obs: Cada modulo segue o conceito do clean arch baseado na divisão de camadas [data, domain, presentation, ...], porém todo o projeto foi modularizado pensando em novas features e cada feature possue suas camadas.
- Flow
- Hilt
- ViewBinding
- Gradle Kts
- AndroidX
- MockK
- Koin (Toda configuração está em uma branch especifica, criada por @jotavier )
o modulo de navigation foi criado para conter todos os contratos de navegação entre os modulos, exemplo:
// :navigation file: FeatureBNavigation.kt
interface FeatureBNavigation {
fun navigationToFeatureB(context: Context, idMovie: Long)
}
e cada modulo deve prover a implementação do contrato e fornecer na arvore de dependencia, assim ensinando como ser chamado.
// :featureB file: FeatureBNavigationImpl.kt
const val EXTRA_ID_MOVIE = "id_movie"
internal class FeatureBNavigationImpl @Inject constructor() : FeatureBNavigation {
override fun navigationToFeatureB(context: Context, idMovie: Long) {
val intent = Intent(context, FeatureBActivity::class.java)
intent.putExtra(EXTRA_ID_MOVIE, idMovie)
context.startActivity(intent)
}
}
fornecendo para injeção de dependencia com Hilt
// :featureB file: NavigationModule.kt
@Module
@InstallIn(SingletonComponent::class)
internal abstract class NavigationModule {
@Binds
abstract fun bindNavigation(
featureBNavigationImpl: FeatureBNavigationImpl
): FeatureBNavigation
}
como chamar a activity do moduloB no moduloA:
// :featureA file: FeatureAActivity.kt
@Inject
lateinit var navigation: FeatureBNavigation
navigation.navigationToFeatureB(this, idMovie = id)
Foi criado o modulo de network para prover toda a configuração do retrofit e seus interceptors:
// :network file: NetworkModule.kt
@Provides
fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor()
.apply {level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY elseHttpLoggingInterceptor.Level.NONE}
@Provides
fun providesDefaultInterceptorQueryParameter() = DefaultQueryParameterInterceptor()
@Provides
fun providesOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,defaultInterceptorQueryParameter: DefaultQueryParameterInterceptor
): OkHttpClient =
OkHttpClient
.Builder()
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(defaultInterceptorQueryParameter)
.build()
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BuildConfig.BASE_URL_TMDB)
.client(okHttpClient)
.build()
Cada modulo de feature deve forncener somente a interface do serviço e solicitar da injeção de dependencia o retrofit provido pelo modulo de network
// :featureA file: FeatureAService.kt
internal interface FeatureAService {
@GET("movie/popular")
suspend fun getPopularMovies(@Query("language") language: String = "pt-BR"): MovieResultResponse
}
// :featureA file: FeatureServiceAModule.kt
@Module
@InstallIn(ViewModelComponent::class)
internal object ServiceAModule {
@Provides
fun provideFeatureAService(retrofit: Retrofit): FeatureAService =
retrofit.create(FeatureAService::class.java)
}
O codigo acima cria uma implementação dos endpoints da API definidos pela interface de serviço. Como usar a interface da api:
// :featureA file: RemoteMovieDataSource.kt
internal class RemoteMovieDataSource @Inject constructor(private val service: FeatureAService) {
fun getPopularMovies(): Flow<MovieResultResponse> = flow {emit(service.getPopularMovies())}
}
Cada modulo feature segue o conceito do clean arch baseado na divisão de camadas, porém todo o projeto foi modularizado pensando em novas features e cada feature pode possuir camadas a mais baseado em sua necessidade.
- Data - responsavel por prover os conteudos e request da API.
- DI - responsavel pela injeção de dependencia do modulo.
- Domain - regras de negocios se houver.
- Navigation - prover navegação para dentro do modulo.
- Presentation - toda camada de view e viewModels.
O modulo commons deve ter a responsabilidade de conter tudo que for comun entre os modulos, exemplo: ExtensionFunctions e tambem injeçoes de depencias comum entre os modulos como os Dispatchers.
TODO
TODO