Arquitetura para projetos
Android
Caique Oliveira
Graduado em Ciência da computação - UFS
Android developer - Stone
Apresentação
Agenda
- Introdução
- Problemas comuns no desenvolvimento
- Arquitetura Limpa(Clean Architecture)
- Dependências úteis
No começo do aprendizado
- Documentação oficial
- Stack Overflow
- Samples do google no github
No começo do aprendizado
- Tudo na activity ou fragment
- Pouca preocupação com organização
- View Einstein
- Não me chamo MainActivity atoa
No começo do aprendizado
- Pouca separação de responsabilidades
- Negócio misturado com framework
Por que isso acontece?
A preocupação é de passar conhecimento sobre determinado assunto
Quais as consequências ?
Código acoplado
Dificuldade em resolver um erro
z23678’z23678
Realmente o erro foi corrigido?
A correção não causa uma nova inconsistência?
Péssima testabilidade
- Ruim de escalar
- Parece um hackathon
Resumindo...
Como melhorar?
- Arquitetura de software
- Aquela disciplina chata da faculdade serve pra alguma coisa!
Arquitetura de software
Como eu posso criar um projeto de forma a não ser xingado por quem pegar
meu código?
Arquitetura de software
Definem estruturas de projeto visando uma boa qualidade de Software
Arquitetura de software
- Desacoplar o código
- Passar a usar o framework ao invés de depender dele
- Separar responsabilidades
- Testabilidade
- Manutenibilidade
- Escalabilidade
Desacoplando do framework
- A view deve apenas exibir informações
- Remover lógica da sua view
- Remover acesso a banco de dados
- Remover request
Einstein Activity
class EinsteinActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
showList()
}
fun showList() {
retrofit.enqueue(object : Callback<Contacts> {
override fun onResponse(call: Call<Contacts>, response: Response<Contacts>) {
// exibe lista
}
override fun onFailure(call: Call<Contacts>, t: Throwable) {
// exibe erro
}
})
}
}
Desacoplando do framework
interface Contract {
interface View {
fun showContacts(contacts: ArrayList<Contacts>)
fun showError(msg: String)
}
}
class MainActivity : AppCompatActivity(), Contract.View {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun showContacts(contacts: ArrayList<Contacts>) {
//exibe lista
}
override fun showError(msg: String) {
//mostra erro
}
}
Desacoplando do framework
interface Contract {
interface View {
fun showContacts(contacts: ArrayList<Contacts>)
fun showError(msg: String)
}
interface Presenter {
fun loadContacts()
}
}
class Presenter : Contract.Presenter {
override fun loadContacts(){
// faz algo aqui
}
}
Mas qual conceito aplicado?
Modelos de arquitetura
- MVP
- MVVM
- Viper
- Clean Architecture
- Hexagonal
- ...
Clean Architecture
- Frameworks são detalhes(rest,banco,etc)
- Casos de Usos são essenciais
- Entidades são essenciais
Clean Architecture
- Depender o mínimo possível de framework
- Depender de comportamentos(interface)
- Núcleo Testável
Dependency rule
Ubiratan Soares
Q?
Vamos lá
- Camadas
- Presentation
- Domain
- Data
Cada camada pode ser um módulo no Android Studio
Domain
- Regras de negócio
- Entidades
- Sabe executar uma operação
- Dependência apenas da linguagem de programação usada
Domain
UseCasePayment.performPayment(...)
UseCaseProfile.updateProfile(...)
UseCaseProfile.uploadPhoto(...)
interface PhotoRepository {
fun photoUpload(string: String, callback: ResultCallback)
}
interface ResultCallback(){
fun success(result:String)
fun 4xx()
fun 5xx()
fun timeOut()
}
data class User(
val id: Int,
val name: String,
val email: String,
val phone: String,
val birthDate: Date,
val photoUrl: String)
Data
- Entrada e saída de dados
- Rest
- Banco de dados
- Cache
- Deve ser fácil de substituir
- Implementação dos contratos definidos pelo Domain(Repository)
class PhotoManager : PhotoRepository {
var photoService: PhotoService
override fun photoUpload(string: String, callbackResult :ResultCallback){
photoService.upload(string).enqueue(object : Callback<String>{
override fun onResponse(call: Call<String>, response:Response<String>) {
callbackResult.success(response.body())
}
override fun onFailure(call: Call<String>, t: Throwable) {
//trata erro e passa para callbackResult
}
})
}
}
interface PhotoRepository {
fun photoUpload(string: String,
callback: ResultCallback)
}
interface ResultCallback(){
fun success(result:String)
fun 4xx()
fun 5xx()
fun timeOut()
}
Presentation
Sabe como se comunicar com o domínio da aplicação
Fornece os dados para o framework
- Data formatada
- Entrega tudo mastigado para a view só exibir
class SearchPresenter() : SearchContract.Presenter {
var useCase = SearchUseCase()
var view: SearchContract.View
override fun search(name: String) {
useCase.execute(name,object : OnComplete {
override fun success(result: String) {
view.showSearch(result)
}
override fun error(t: Throwable) {
view.showError(“msg”)
}
})
}
}
interface OnComplete {
fun success(result: String)
fun error(t: Throwable)
}
Establishment{
id = 123
address{
address = "R. Visconde",
number = "414",
state = "Rio de Janeiro",
city = "Rio de Janeiro",
district = "Ipanema",
zip_code = "22410-002",
latitude = 22.9837684,
longitude = -43.2074878
}
name = "Estabelecimento do
bolinha"
status : enum = [open, close]
}
Data Domain Presentation
Establishment{
id = 123
address{
"address": "R. Visconde",
"number": "414",
"state": "Rio de Janeiro",
"city": "Rio de Janeiro",
"district": "Ipanema",
"zip_code": "22410-002"
}
name = "Estabelecimento
do bolinha"
distance = 1400
status= true
}
Establishment{
id = 123
address=”Rua visconde, 414, Rio
de janeiro - Rj”
name = "Estabelecimento do
bolinha"
distance = 1,4Km
status= “Aberto”
}
Organização de pacotes
Passo a passo
Passo a passo
Defina uma interface para
repository
Defina seus casos de uso e
suas entidades
interface ResultCallback{
fun success(profile:Profile)
fun 4xx()
fun 5xx()
fun error()
}
interface AuthenticationRepository{
fun login(credential: Credential, callback:ResultCallback)
}
class LoginUseCase(){
fun login(credentials: Credentials){
return authenticationRepository.login(credentials,callback)
}
}
data class Profile(var name: String, var age : Int)
data class Credential(var email: String, var password)
Passo a passo
Implemente seu repository
interface Service {
@Get(...)
fun signIn(@Body credential: Credential): Call<ProfileResponse>
}
class AuthenticatorManager(): AuthenticationRepository{
val service : Service
override fun login(credential,callback){
service.signIn(credential).enqueue(
success(profileResponse){
callback.success(Mapper.toProfile(responsse))
}
error(){
// controle o erro aqui
})
}
}
Passo a passo
Defina o contrato para
View e Presenter
interface Contract {
inteface View(){
fun showProfile(profile: ProfilePresentation)
}
interface Presenter {
fun attachView(view: View)
fun login(email: String, password: String)
}
}
Passo a passo
Implemente o presenter
inteface OnComplete{
fun onSuccess()
fun onError()
}
class Presenter(loginUseCase: LoginUseCase): Contract.Presenter{
val view : View?=null
override attachView(view : View){
this.view = view
}
override login(email: String, password : String){
loginUseCase.login(Credential(email,password),onComplete{
onSuccess(response){
view?.showProfile(Mapper.toProfilePresenter(response))
}
onError(throwable : Throwable){
//trata erro aqui
}
})
}
Passo a passo
Implemente o contrato
da View
class ProfileFragment : Fragment(), Contract.View{
val presenter : Contract.Presenter ?= null
override fun onViewCreated(....) {
super.onViewCreated(....)
presenter = Presenter(...)
presenter.attachView(this)
}
override showProfile(profile: ProfilePresentation){
nameTextView.text = profile.name
ageTextView.text = profile.age
}
override showErrorLogin(msg: String){
toast(msg)
}
@OnClick(R.id.loginButton)
fun onClick(){
presenter.login(emailEditText.text.toString, passwordEditText.text.toString())
}
}
Dependências úteis
Dagger 2
Rxjava 2
Dúvidas?
Exemplo clean
https://goo.gl/R2MzkK
Referências
Escaping from framework(Ubiratan soares)
https://speakerdeck.com/ubiratansoares/escaping-from-the-framework
The clean architecture(Uncle Bob):
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
Obrigado!
twitter: @_josecaique
email: jcaique.jc@gmail.com
slack android dev br: caique
github: jcaiqueoliveira
https://goo.gl/xjnnWs
Temos vagas!
enter.stone.com.br

Android Dev Conference 2017 - Arquitetura para projetos Android

  • 1.
  • 2.
    Caique Oliveira Graduado emCiência da computação - UFS Android developer - Stone Apresentação
  • 3.
    Agenda - Introdução - Problemascomuns no desenvolvimento - Arquitetura Limpa(Clean Architecture) - Dependências úteis
  • 4.
    No começo doaprendizado - Documentação oficial - Stack Overflow - Samples do google no github
  • 5.
    No começo doaprendizado - Tudo na activity ou fragment - Pouca preocupação com organização - View Einstein - Não me chamo MainActivity atoa
  • 6.
    No começo doaprendizado - Pouca separação de responsabilidades - Negócio misturado com framework
  • 7.
    Por que issoacontece? A preocupação é de passar conhecimento sobre determinado assunto
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
    Realmente o errofoi corrigido? A correção não causa uma nova inconsistência?
  • 13.
  • 14.
    - Ruim deescalar - Parece um hackathon
  • 15.
  • 16.
    Como melhorar? - Arquiteturade software - Aquela disciplina chata da faculdade serve pra alguma coisa!
  • 17.
    Arquitetura de software Comoeu posso criar um projeto de forma a não ser xingado por quem pegar meu código?
  • 18.
    Arquitetura de software Definemestruturas de projeto visando uma boa qualidade de Software
  • 19.
    Arquitetura de software -Desacoplar o código - Passar a usar o framework ao invés de depender dele - Separar responsabilidades - Testabilidade - Manutenibilidade - Escalabilidade
  • 20.
    Desacoplando do framework -A view deve apenas exibir informações - Remover lógica da sua view - Remover acesso a banco de dados - Remover request
  • 21.
    Einstein Activity class EinsteinActivity:AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { showList() } fun showList() { retrofit.enqueue(object : Callback<Contacts> { override fun onResponse(call: Call<Contacts>, response: Response<Contacts>) { // exibe lista } override fun onFailure(call: Call<Contacts>, t: Throwable) { // exibe erro } }) } }
  • 22.
    Desacoplando do framework interfaceContract { interface View { fun showContacts(contacts: ArrayList<Contacts>) fun showError(msg: String) } } class MainActivity : AppCompatActivity(), Contract.View { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } override fun showContacts(contacts: ArrayList<Contacts>) { //exibe lista } override fun showError(msg: String) { //mostra erro } }
  • 23.
    Desacoplando do framework interfaceContract { interface View { fun showContacts(contacts: ArrayList<Contacts>) fun showError(msg: String) } interface Presenter { fun loadContacts() } } class Presenter : Contract.Presenter { override fun loadContacts(){ // faz algo aqui } }
  • 24.
  • 26.
    Modelos de arquitetura -MVP - MVVM - Viper - Clean Architecture - Hexagonal - ...
  • 27.
    Clean Architecture - Frameworkssão detalhes(rest,banco,etc) - Casos de Usos são essenciais - Entidades são essenciais
  • 28.
    Clean Architecture - Dependero mínimo possível de framework - Depender de comportamentos(interface) - Núcleo Testável
  • 29.
  • 30.
  • 31.
  • 32.
    Vamos lá - Camadas -Presentation - Domain - Data
  • 33.
    Cada camada podeser um módulo no Android Studio
  • 34.
    Domain - Regras denegócio - Entidades - Sabe executar uma operação - Dependência apenas da linguagem de programação usada
  • 35.
  • 36.
    interface PhotoRepository { funphotoUpload(string: String, callback: ResultCallback) } interface ResultCallback(){ fun success(result:String) fun 4xx() fun 5xx() fun timeOut() } data class User( val id: Int, val name: String, val email: String, val phone: String, val birthDate: Date, val photoUrl: String)
  • 37.
    Data - Entrada esaída de dados - Rest - Banco de dados - Cache - Deve ser fácil de substituir - Implementação dos contratos definidos pelo Domain(Repository)
  • 38.
    class PhotoManager :PhotoRepository { var photoService: PhotoService override fun photoUpload(string: String, callbackResult :ResultCallback){ photoService.upload(string).enqueue(object : Callback<String>{ override fun onResponse(call: Call<String>, response:Response<String>) { callbackResult.success(response.body()) } override fun onFailure(call: Call<String>, t: Throwable) { //trata erro e passa para callbackResult } }) } } interface PhotoRepository { fun photoUpload(string: String, callback: ResultCallback) } interface ResultCallback(){ fun success(result:String) fun 4xx() fun 5xx() fun timeOut() }
  • 39.
    Presentation Sabe como secomunicar com o domínio da aplicação Fornece os dados para o framework - Data formatada - Entrega tudo mastigado para a view só exibir
  • 40.
    class SearchPresenter() :SearchContract.Presenter { var useCase = SearchUseCase() var view: SearchContract.View override fun search(name: String) { useCase.execute(name,object : OnComplete { override fun success(result: String) { view.showSearch(result) } override fun error(t: Throwable) { view.showError(“msg”) } }) } } interface OnComplete { fun success(result: String) fun error(t: Throwable) }
  • 41.
    Establishment{ id = 123 address{ address= "R. Visconde", number = "414", state = "Rio de Janeiro", city = "Rio de Janeiro", district = "Ipanema", zip_code = "22410-002", latitude = 22.9837684, longitude = -43.2074878 } name = "Estabelecimento do bolinha" status : enum = [open, close] } Data Domain Presentation Establishment{ id = 123 address{ "address": "R. Visconde", "number": "414", "state": "Rio de Janeiro", "city": "Rio de Janeiro", "district": "Ipanema", "zip_code": "22410-002" } name = "Estabelecimento do bolinha" distance = 1400 status= true } Establishment{ id = 123 address=”Rua visconde, 414, Rio de janeiro - Rj” name = "Estabelecimento do bolinha" distance = 1,4Km status= “Aberto” }
  • 43.
  • 44.
  • 45.
    Passo a passo Definauma interface para repository Defina seus casos de uso e suas entidades interface ResultCallback{ fun success(profile:Profile) fun 4xx() fun 5xx() fun error() } interface AuthenticationRepository{ fun login(credential: Credential, callback:ResultCallback) } class LoginUseCase(){ fun login(credentials: Credentials){ return authenticationRepository.login(credentials,callback) } } data class Profile(var name: String, var age : Int) data class Credential(var email: String, var password)
  • 46.
    Passo a passo Implementeseu repository interface Service { @Get(...) fun signIn(@Body credential: Credential): Call<ProfileResponse> } class AuthenticatorManager(): AuthenticationRepository{ val service : Service override fun login(credential,callback){ service.signIn(credential).enqueue( success(profileResponse){ callback.success(Mapper.toProfile(responsse)) } error(){ // controle o erro aqui }) } }
  • 47.
    Passo a passo Definao contrato para View e Presenter interface Contract { inteface View(){ fun showProfile(profile: ProfilePresentation) } interface Presenter { fun attachView(view: View) fun login(email: String, password: String) } }
  • 48.
    Passo a passo Implementeo presenter inteface OnComplete{ fun onSuccess() fun onError() } class Presenter(loginUseCase: LoginUseCase): Contract.Presenter{ val view : View?=null override attachView(view : View){ this.view = view } override login(email: String, password : String){ loginUseCase.login(Credential(email,password),onComplete{ onSuccess(response){ view?.showProfile(Mapper.toProfilePresenter(response)) } onError(throwable : Throwable){ //trata erro aqui } }) }
  • 49.
    Passo a passo Implementeo contrato da View class ProfileFragment : Fragment(), Contract.View{ val presenter : Contract.Presenter ?= null override fun onViewCreated(....) { super.onViewCreated(....) presenter = Presenter(...) presenter.attachView(this) } override showProfile(profile: ProfilePresentation){ nameTextView.text = profile.name ageTextView.text = profile.age } override showErrorLogin(msg: String){ toast(msg) } @OnClick(R.id.loginButton) fun onClick(){ presenter.login(emailEditText.text.toString, passwordEditText.text.toString()) } }
  • 50.
  • 51.
  • 52.
  • 53.
    Referências Escaping from framework(Ubiratansoares) https://speakerdeck.com/ubiratansoares/escaping-from-the-framework The clean architecture(Uncle Bob): https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
  • 54.
    Obrigado! twitter: @_josecaique email: jcaique.jc@gmail.com slackandroid dev br: caique github: jcaiqueoliveira https://goo.gl/xjnnWs
  • 55.