改进Pokage架构©DEX
#kotlin #android #cleancode #pokemon

在这篇文章中,我们将使用Koin来委派我们应用程序的依赖性,例如Raterofit,API服务,重新启动和ViewModels,并在我们的项目中使用接口和用例实现干净的体系结构。

>

干净的体系结构

a 干净的体系结构(清洁体系结构)是一个名称,旨在专注于应用程序领域的软件开发体系结构;作为驱动程序,框架和磅数仅细节。目的是责任原则,将利益与每项义务分开,并维护业务规则,而无需了解外界的任何细节;因此,可以在不依赖任何外部元素的情况下对其进行测试。

Clean Architecture

总而言之,我们的系统将分为反式层:

  • 演示文稿(Android Mother):负责应用程序接口和从域接收的数据显示。
  • 域(Kotlin Mother):负责该项目特定领域的实体和规则。这个母亲应该完全独立于平台 android。
  • 基础架构(Android母亲):负责数据库,互联网访问和应用程序的其他详细信息。

演示层中我们具有 Active 片段,其中必须没有羊毛不是UI宽的。

另一方面,在域中层层,座位的接口和用例,这是我们的业务,并服务作为演示基础架构之间的桥梁

最后,在基础架构层中是我们应用程序的必要数据(在我们的情况下称为API重置),可以从储存库中访问>域。

什么变化?好吧,我们正在做这样的事情,但是我们的存储库并未在合同(接口)和实施中分开,我们甚至没有使用案例访问 repositors 的实施。并将数据发送到演示

要团结所有这些转移并使体系结构起作用,让我们使用Koin框架应用依赖性反转原理(逆转原理), d 实心。该原则说,高潜水员不能依赖下游的转移。两者都必须取决于抽象,抽象不应取决于细节。详细信息应取决于抽象。

也就是说,在我们的几个部分中,存储库负责创建A service 的实例,A viewModel 它负责创建A *存储库的实例,现在更改。

注意:清洁体系结构的说明Marcello Galhardo的优质artigo中删除。

使用Koud0

Koin是根据Kotlin完全编写的注射框架的轻型框架,非常容易学习和使用。要使用它,我们需要了解您的术语:

  • 模块:创建可用于提供全部依赖的Koin职责。
  • 单身:创建一个可以在整个应用程序中用作唯一实例的单例。
  • Factory :提供一个bean定义,每次注入新实例。
  • get():用于提供必要依赖性的类构建器中。

现在让我们通过Koud7文件将Koud0添加到我们的项目中:

def koinVersion = "3.2.2"

dependencies {
    ...

  // Koin
  implementation "io.insert-koin:koin-android:$koinVersion"
  implementation "io.insert-koin:koin-android-compat:$koinVersion"
  implementation "io.insert-koin:koin-androidx-workmanager:$koinVersion"
  implementation "io.insert-koin:koin-androidx-navigation:$koinVersion" 

    ...
}

要开始在我们的项目中使用Koin,首先,我们需要为我们的PokemonRepository创建一个界面,我们这样做以遵守干净的体系结构的原理。

package br.com.pokedex.domain.repository

import br.com.pokedex.domain.model.SinglePokemon

interface PokemonRepository {

    suspend fun getSinglePokemon(id: Int): SinglePokemon
}

PokemonRepository类将重命名为koud11并修改:

package br.com.pokedex.data.repository

import br.com.pokedex.data.api.PokemonApi
import br.com.pokedex.data.mapper.toModel
import br.com.pokedex.domain.repository.PokemonRepository

class PokemonRepositoryImpl(private val api: PokemonApi) : PokemonRepository {

    override suspend fun getSinglePokemon(id: Int) = api.getSinglePokemon(id).toModel()
}

请注意,我们在此处删除了 service 的创建,我们之所以这样做,是因为Koin将负责注入此依赖性,因此请应用依赖性逆转的原理。

我们将转到创建用例的创建,该将用作存储库和 viewModel 之间的桥梁。目前,它将非常简单,只会具有execute()函数,该功能将访问koud9并返回koud15:

package br.com.pokedex.domain.interactor

import br.com.pokedex.domain.model.SinglePokemon
import br.com.pokedex.domain.repository.PokemonRepository

class GetSinglePokemonUseCase(private val repository: PokemonRepository) {

    suspend fun execute(id: Int) : SinglePokemon {
        return repository.getSinglePokemon(id)
    }
}

package br.com.pokedex.presentation

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import br.com.pokedex.domain.interactor.GetSinglePokemonUseCase
import br.com.pokedex.domain.model.SinglePokemon
import br.com.pokedex.domain.repository.PokemonRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private const val MIN_POKEMON_ID = 1
private const val MAX_POKEMON_ID = 151

class PokedexViewModel(private val useCase: GetSinglePokemonUseCase) : ViewModel() {

    private val _pokemon = MutableLiveData<List<SinglePokemon>>()
    val pokemon: LiveData<List<SinglePokemon>>
        get() = _pokemon

    fun getPokemon() {
        viewModelScope.launch(Dispatchers.IO) {
            val data = mutableListOf<SinglePokemon>()

            for (i in MIN_POKEMON_ID..MAX_POKEMON_ID) {
                data.add(useCase.execute(i))
            }

            withContext(Dispatchers.Main) {
                _pokemon.postValue(data.toList())
            }
        }
    }

}

好吧,让我们开始有效地使用koud0。最初,我们将创建一个称为di的软件包(用于依赖依赖项注入的缩写),我们将构建依赖性注射文件。这是我们创建了InfrastructureModule.kt文件,我们将创建一些在重要依赖的实例中进行的一些功能:RetrofitPokemonApi

package br.com.pokedex.di

import br.com.pokedex.BuildConfig
import br.com.pokedex.data.api.PokemonApi
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

private fun providePokemonApi(retrofit: Retrofit): PokemonApi {
    return retrofit.create(PokemonApi::class.java)
}

private fun provideRetrofit(): Retrofit {
    return Retrofit.Builder().run {
        addConverterFactory(GsonConverterFactory.create())
        baseUrl(BuildConfig.POKE_API)
        build()
    }
}

请注意,在provideRetrofit()中,我们参考了一个POKE_API字符串,它是pokâapi的基本URL,我们这样做是将其与下跌相距,我们将其放在koud7文件中:

def POKE_API = "POKE_API"
def URL_BASE_POKE_API = "\"https://pokeapi.co/api/v2/\""

android {
        ...
    buildTypes {

        debug {
            applicationIdSuffix ".dev"
            debuggable true
            buildConfigField "String", POKE_API, URL_BASE_POKE_API
        }

    }
    ...
}

此时,当我们提供这些提供这些时,我们可以建立将为应用程序提供这些依赖性的沉闷,让我们称他为InfrastructureModule,他将是一个依赖的集合每个函数将使用函数factory返回,然后在必要时注射:

package br.com.pokedex.di

import br.com.pokedex.BuildConfig
import br.com.pokedex.data.api.PokemonApi
import br.com.pokedex.data.repository.PokemonRepositoryImpl
import br.com.pokedex.domain.repository.PokemonRepository
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

fun infrastructureModule() = module {
    factory { provideRetrofit() }
    factory { providePokemonApi(get()) }
    factory<PokemonRepository> { PokemonRepositoryImpl(get()) }
}

private fun providePokemonApi(retrofit: Retrofit): PokemonApi {
    return retrofit.create(PokemonApi::class.java)
}

private fun provideRetrofit(): Retrofit {
    return Retrofit.Builder().run {
        addConverterFactory(GsonConverterFactory.create())
        baseUrl(BuildConfig.POKE_API)
        build()
    }
}

请注意,我们对职责进行编程,以便当请求PokemonRepository实例时,我们将返回PokemonRepositoryImpl的实例,使我们仅参考课程中的抽象,而不是实现£O。

仅剩下两个依赖性:GetSinglePokemonUseCasePokedexViewModel,我们继续开发其他两个文件:DomainModule.kt,for case 和koud32,用于 viewModel

package br.com.pokedex.di

import br.com.pokedex.domain.interactor.GetSinglePokemonUseCase
import org.koin.dsl.module

fun domainModule() = module {
    factory { GetSinglePokemonUseCase(get()) }
}
package br.com.pokedex.di

import br.com.pokedex.presentation.PokedexViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

fun presentationModule() = module {
    viewModel { PokedexViewModel(get()) }
}
因此,我们已经准备好接受过的妈妈被注射。要执行此注射,我们将建立一个将成为我们项目根源的课程,Koud33:

package br.com.pokedex

import android.app.Application

class Pokedex : Application() {}

它是Application子类,因为它是包含所有其他组件的应用程序的基类,例如Actives 和 services ,它是在其他类之前实例化的我们的应用程序是创建的。而且我们不能忘记将其添加到Koud35:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    ...

    <application
        android:name=".Pokedex"
        ...
        >

        ...

    </application>

</manifest>

现在,让我们创建将处理所有这些依赖性的文件,即Injector.kt,在其中我们开发了一个扩展函数 Application称为koud38,该函数以所有必要的依赖性启动koud0:

package br.com.pokedex.di

import android.app.Application
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin

fun Application.inject() {
    startKoin {
        androidLogger()
        androidContext(this@inject)
        modules(getModules())
    }
}

fun getModules() = listOf(
    infrastructureModule(),
    domainModule(),
    presentationModule()
)

除了使用startKoin函数外,我们还使用androidLogger(),它提供了一个简单的API来执行与KoinandroidContext()相关的日志,该日志将Context添加到Koud0 coantment,modules(),以携带职责的定义,并Koud47,他返回我们创建的所有转移。

好吧,我们已经可以在我们的Application类中插入此函数,在onCreate()函数中给予override

package br.com.pokedex

import android.app.Application
import br.com.pokedex.di.inject

class Pokedex : Application() {

    override fun onCreate() {
        super.onCreate()
        inject()
    }
}

因此,我们的项目以这种方式结构:

Estrutura de pastas

APON运行该应用程序,让我们意识到没有视觉上没有任何变化,但是我们知道我们的项目结构更好,没有核心,遵守了干净的体系结构的prinome!

帖子

在下一篇文章中,我们将使用分页库和Flow API提高Dex的性能,此外还可以使它们更美丽。

repositgoly no github:

GitHub logo ronaldocoding / pokedex

简单的粉末

Pokédex

A simple Pokédex

前邮: