SlideShare uma empresa Scribd logo
1 de 62
Baixar para ler offline
Arquitetando seu app
Android com Jetpack
Nelson Glauber
@nglauber
Porque devemos nos
preocupar com arquitetura?
‣ Frameworks forçam o desenvolvedor a seguir o próprio
framework e não os princípios da engenharia de
software.
‣ Sua arquitetura deve deixar claro o propósito do sistema,
não os frameworks utilizados.
‣ A lógica de negócio deve estar claramente separada e
independente de framework.
Arquitetura
‣ Regra Principal: não há regras. Mas existem princípios que
devem ser seguidos. Lembra do S.O.L.I.D.?
‣ Promove a organização e o desacoplamento do código.
‣ Deve facilitar a manutenção e a adição de novas
funcionalidades.
‣ Uma arquitetura deve ser testável!
‣ Ela incrementa a complexidade? Sim! Vale à pena? Com
certeza! 😎 Mas deve ser de conhecimento de toda à equipe.
Single Responsibility Principle
Open-Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
MVP
View
IView
Presenter Model
User interaction Request
DataUI Logic
implements
IPresenter
implements
MVVM
View ViewModel Model
Observes
User interaction
Data
Request
ordem das letras MVVM e MVP não faz sentido
https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
androidx.*
https://github.com/nglauber/books_jetpack/
UI (app)
Presentation
Domain
Remote Local
Data
UI (app)
Presentation
Domain
Remote Local
Data
‣ Módulo Kotlin
‣ Normalmente contém classes de
dados e interfaces.
‣ Decisão importante aqui: esse
app será reativo? 🤔
Data
RX Java
Coroutines Flow
https://bit.ly/2KwSkDs
interface BooksRepository {
fun loadBooks(): Flow<List<Book>>
fun loadBook(bookId: String): Flow<Book>
suspend fun saveBook(book: Book)
suspend fun remove(book: Book)
}
UI (app)
Presentation
Domain
Remote Local
Data
Local
‣ Módulo Android
‣ Faz a implementação do
repositório local
‣ Book do módulo data é diferente
do Book deste módulo
Room
‣ ORM (Object-Relational Mapping) para
SQLite.
‣ Suporta live updates por meio de
LiveData, RXJava (Observable/Flowable)
e Coroutines (suspend/flow)
Local
@Entity
@TypeConverters(MediaTypeConverter::class)
data class Book(
@PrimaryKey
var id: String,
var title: String = "",
var author: String = "",
var coverUrl: String = "",
var pages: Int = 0,
var year: Int = 0,
@Embedded(prefix = "publisher_")
var publisher: Publisher,
var available: Boolean = false,
var mediaType: MediaType = MediaType.PAPER,
var rating: Float = 0f
)
@Dao
interface BookDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(book: Book)
@Delete
suspend fun delete(vararg book: Book)
@Query("SELECT * FROM Book WHERE title LIKE :title ORDER BY title")
fun bookByTitle(title: String = "%"): Flow<List<Book>>
@Query("SELECT * FROM Book WHERE id = :id")
fun bookById(id: String): Flow<Book>
}
@Database(entities = [Book::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun bookDao(): BookDao
companion object {
private var instance: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"booksDb")
.build()
}
return instance as AppDatabase
}
}
}
class RoomRepository(
db: AppDatabase,
private val fileHelper: FileHelper
) : BooksRepository {
private val bookDao = db.bookDao()
override suspend fun saveBook(book: Book) {
if (book.id.isBlank()) {
book.id = UUID.randomUUID().toString()
}
return if (fileHelper.saveCover(book)) {
bookDao.save(BookConverter.fromData(book))
} else {
throw RuntimeException("Error saving book's cover.")
}
}

...
class RoomRepository(
db: AppDatabase,
private val fileHelper: FileHelper
) : BooksRepository {
private val bookDao = db.bookDao()
override fun loadBooks(): Flow<List<Book>> {
return bookDao.bookByTitle()
.map { books ->
books.map { book ->
BookConverter.toData(book)
}
}
}
...
UI (app)
Presentation
Domain
Remote Local
Data
‣ Módulo Kotlin
‣ Implementação da lógica de
maneira abstrata
Domain
open class ListBooksUseCase(
private val repository: BooksRepository
) {
fun execute(): Flow<List<Book>> {
return repository.loadBooks()
}
}
open class SaveBookUseCase(
private val repository: BooksRepository
) {
suspend fun execute(params: Book) {
return if (bookIsValid(params)) {
repository.saveBook(params)
} else {
throw IllegalArgumentException("Book is not valid")
}
}
private fun bookIsValid(book: Book): Boolean {
return (
book.title.isNotBlank() &&
book.author.isNotBlank() &&
book.pages > 0 &&
book.year > 1900 &&
book.year <= Calendar.getInstance().get(Calendar.YEAR)
)
}
}
UI (app)
Presentation
Domain
Remote Local
Data
Data Binding
• Torna fácil a conexão entre a
View e o Presenter/ViewModel.
• Permite extender arquivos de
layout com micro-expressões.
• Muito útil em telas onde há input
de dados.
Presentation
@Parcelize
class Book : BaseObservable(), Parcelable {
@Bindable
var id: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.id)
}
@Bindable
var title: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.title)
}
// demais atributos
}
<layout ...>
<data> ...
<import type="dominando.android.presentation.binding.MediaType" />
<variable name="book" type="dominando.android.presentation.binding.Book" />
<variable name="presenter" type="dominando.android.livros.BookFragment" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<ImageView android:src=“@{book.coverUrl}" ...
android:onClick="@{presenter::clickTakePhoto}"/>
<EditText android:text="@={book.title}" ... />
<EditText android:text="@={book.author}" ... />
<EditText android:text="@={book.pages}" ... />
<EditText android:text="@={book.year}" ... />
<Spinner ...>
<CheckBox android:checked="@={book.available}" ... />
<RadioGroup ...>
<RadioButton ...
android:checked="@{book.mediaTypeValue == MediaType.EBOOK}" ... />
<RadioButton ...
android:checked="@{book.mediaTypeValue == MediaType.PAPER}" ... />
</RadioGroup>
<RatingBar android:rating="@={book.rating}" ... />
<Button android:onClick="@{presenter::clickSaveBook}" .../>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
res/layout/fragment_book_form.xml
class BookFormFragment : BaseFragment() {
private lateinit var binding: FragmentBookFormBinding
override fun onCreateView ... {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_book_form,
container,
false
)
return binding.root
}
override fun onViewCreated ... {
binding.book = viewModel.book
binding.presenter = this
}
...
ViewModel
Presentation
LiveData
• LiveData stores observable data
(Observable) and notify the observers
(Observer) when the data changes
allowing the UI to be updated.
• LiveData is lifecycle-aware, which means
that it only notify the observers if the
Activity/Fragment are either in STARTED
or RESUMED state.
Presentation
class BookDetailsViewModel(private val useCase: ViewBookDetailsUseCase) : ViewModel() {
private val state: MutableLiveData<ViewState<BookBinding>> = MutableLiveData()
fun getState(): LiveData<ViewState<BookBinding>> = state
fun loadBook(id: String) {
...
viewModelScope.launch {
state.postValue(ViewState(ViewState.Status.LOADING))
try {
useCase.execute(id).collect { book ->
if (book != null) {
val bookBinding = BookConverter.fromData(book)
state.postValue(ViewState(ViewState.Status.SUCCESS, bookBinding))
} else {
state.postValue(
ViewState(ViewState.Status.ERROR, RuntimeException("Book not found"))
)
}
}
} catch (e: Exception) {
state.postValue(ViewState(ViewState.Status.ERROR, error = e))
}
}
}
class BookDetailsFragment : BaseFragment() {
private val viewModel: BookDetailsViewModel by viewModels {
BookVmFactory(requireActivity().application)
}
…
private fun init() {
viewModel.getState().observe(viewLifecycleOwner, Observer { viewState ->
when (viewState.status) {
ViewState.Status.SUCCESS -> binding.book = viewState.data
ViewState.Status.LOADING -> {} /* TODO */
ViewState.Status.ERROR -> {}/* TODO */
}
})
val book = arguments?.getParcelable<Book>("book")
book?.let {
viewModel.loadBook(book.id)
}
}
Presentation
open class LiveEvent<out T>(private val content: T) {
var hasBeenConsumed = false
private set
fun consumeEvent(): T? {
return if (hasBeenConsumed) {
null
} else {
hasBeenConsumed = true
content
}
}
fun peekContent(): T = content
}
class BookFormViewModel(
private val useCase: SaveBookUseCase
) : ViewModel(), LifecycleObserver {
private val state: MutableLiveData<LiveEvent<ViewState<Unit>>> = MutableLiveData()
fun getState(): LiveData<LiveEvent<ViewState<Unit>>> = state
fun saveBook(book: BookBinding) {
state.postValue(LiveEvent(ViewState(ViewState.Status.LOADING)))
viewModelScope.launch {
try {
withContext(Dispatchers.IO) {
useCase.execute(BookConverter.toData(book))
}
state.postValue(LiveEvent(ViewState(ViewState.Status.SUCCESS)))
} catch (e: Exception) {
state.postValue(LiveEvent(ViewState(ViewState.Status.ERROR, error = e)))
}
}
}
class BookFormFragment : BaseFragment() {
...
private fun init() {
viewModel.getState().observe(this, Observer { event ->
event?.peekContent()?.let { state ->
when (state.status) {
ViewState.Status.LOADING -> {...}
ViewState.Status.SUCCESS -> {...}
ViewState.Status.ERROR -> {
...
event.consumeEvent()
}
}
}
})
}
Lifecycle
‣ Lifecycle is an object which defines a life cycle.
‣ LifecycleOwner is the interface to be implemented by
objects with a life cycle
‣ Activity and Fragment implements LifecycleOwner
and has Lifecycle.
‣ LifecycleObserver is the interface to be implemented
by who wants to observe a LifecycleOwner.
class BookListViewModel(
private val loadBooksUseCase: ListBooksUseCase, ...
) : ViewModel(), LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun loadBooks() {
if (state.value == null) {
viewModelScope.launch {
state.postValue(ViewState(ViewState.Status.LOADING))
try {
loadBooksUseCase.execute()
.collect { books ->
val booksBinding = books.map { book ->
BookConverter.fromData(book)
}
state.postValue(
ViewState(ViewState.Status.SUCCESS, booksBinding)
)
}
} catch (e: Exception) {
state.postValue(ViewState(ViewState.Status.ERROR, error = e))
}
}
}
}
class BookListFragment : BaseFragment() {
private val viewModel: BookListViewModel
private fun init() {
...
lifecycle.addObserver(viewModel)
}
...
UI (app)
Presentation
Domain
Remote Local
Data
UI (app)
Navigation API
• Introduz o conceito de “Single Activity”.
• Centraliza a lógica de navegação da
aplicação.
• Permite a conexão direta com
componentes de UI, tais como: ActionBar,
Button, Menu, BottomNav, …
• Simplifica a passagem e atribuição de
parâmetros (sem mais método
newInstance).
<navigation ...
android:id="@+id/main_graph"
app:startDestination="@id/signInFragment">
<fragment
android:id="@+id/listBooks"
android:name="dominando.android.livros.BookListFragment"
android:label="@string/app_name"
tools:layout="@layout/fragment_book_list">
<action
android:id="@+id/action_list_to_form"
app:destination="@id/formBook" ... />
...
</fragment>
<fragment
android:id="@+id/bookDetails"
android:name="dominando.android.livros.BookDetailsFragment"
android:label="@string/app_name"
tools:layout="@layout/fragment_book_details">
...
<argument
android:name="book"
app:argType="dominando.android.presentation.binding.Book"
app:nullable="true" />
</fragment>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<fragment
android:id="@+id/navHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
class BookActivity : AppCompatActivity() {
private val navController: NavController by lazy {
Navigation.findNavController(this, R.id.navHost)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book)
NavigationUI.setupActionBarWithNavController(this, navController)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp()
}
}
// BookListFragment
val args = Bundle().apply {
putParcelable("book", book)
}
navController.navigate(R.id.action_list_to_details, args)
// BookDetailsFragment
val book = arguments?.getParcelable<Book>("book")
binding.book = book
#
#
🧐 #
#
#
#
🧐 #
🧐 #
Bonus Topics
• Organização do projeto: por pacote ou por feature?
• Injeção de Dependência: Dagger x Koin
• Testes (JUnit, Espresso, MockK, Hamcrest, …)
• Lint & KtLint
• Git Hooks
github.com/googlesamples/android-architecture
• Você não precisa usar tudo isso na
sua aplicação
• Na verdade, você não precisa usar
nenhum!
• O importante é saber QUANDO USAR
is QUANDO NÃO USAR 😉
• É essencial conhecer esses tópicos,
seus prós e contras e usá-los
adequadamente 💡
References
• Clean Architecture

https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-
architecture.html
• Android Architecture Components

https://developer.android.com/topic/libraries/architecture/
• LiveData beyond the ViewModel

https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-
reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7
• Android Architecture (Five Agency)

https://five.agency/android-architecture-part-1-every-new-beginning-is-hard/
• Joe Birch (@hitherejoe) Course at Caster.io

https://caster.io/courses/android-clean-architecture
Obrigado!
Nelson Glauber
@nglauber

Mais conteúdo relacionado

Mais procurados

Server Side JavaScript - You ain't seen nothing yet
Server Side JavaScript - You ain't seen nothing yetServer Side JavaScript - You ain't seen nothing yet
Server Side JavaScript - You ain't seen nothing yet
Tom Croucher
 
Android Support Library
Android Support LibraryAndroid Support Library
Android Support Library
Alexey Ustenko
 
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
Provectus
 
Beginning icloud development - Cesare Rocchi - WhyMCA
Beginning icloud development - Cesare Rocchi - WhyMCABeginning icloud development - Cesare Rocchi - WhyMCA
Beginning icloud development - Cesare Rocchi - WhyMCA
Whymca
 

Mais procurados (20)

Server Side JavaScript - You ain't seen nothing yet
Server Side JavaScript - You ain't seen nothing yetServer Side JavaScript - You ain't seen nothing yet
Server Side JavaScript - You ain't seen nothing yet
 
2015 05 27 JSConf - concurrency and parallelism final
2015 05 27   JSConf - concurrency and parallelism final2015 05 27   JSConf - concurrency and parallelism final
2015 05 27 JSConf - concurrency and parallelism final
 
Android Support Library
Android Support LibraryAndroid Support Library
Android Support Library
 
New Design of OneRing
New Design of OneRingNew Design of OneRing
New Design of OneRing
 
soft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.js
 
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
 
Node.js in action
Node.js in actionNode.js in action
Node.js in action
 
Building Android games using LibGDX
Building Android games using LibGDXBuilding Android games using LibGDX
Building Android games using LibGDX
 
7주 JavaScript 실습
7주 JavaScript 실습7주 JavaScript 실습
7주 JavaScript 실습
 
What is nodejs
What is nodejsWhat is nodejs
What is nodejs
 
Activator and Reactive at Play NYC meetup
Activator and Reactive at Play NYC meetupActivator and Reactive at Play NYC meetup
Activator and Reactive at Play NYC meetup
 
¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?¿Cómo de sexy puede hacer Backbone mi código?
¿Cómo de sexy puede hacer Backbone mi código?
 
Client-side MVC with Backbone.js
Client-side MVC with Backbone.js Client-side MVC with Backbone.js
Client-side MVC with Backbone.js
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
 
Beginning icloud development - Cesare Rocchi - WhyMCA
Beginning icloud development - Cesare Rocchi - WhyMCABeginning icloud development - Cesare Rocchi - WhyMCA
Beginning icloud development - Cesare Rocchi - WhyMCA
 
DrupalCon jQuery
DrupalCon jQueryDrupalCon jQuery
DrupalCon jQuery
 
Puppet modules for Fun and Profit
Puppet modules for Fun and ProfitPuppet modules for Fun and Profit
Puppet modules for Fun and Profit
 
jQuery: Events, Animation, Ajax
jQuery: Events, Animation, AjaxjQuery: Events, Animation, Ajax
jQuery: Events, Animation, Ajax
 
Beyond the Callback: Yield Control with Javascript Generators
Beyond the Callback: Yield Control with Javascript GeneratorsBeyond the Callback: Yield Control with Javascript Generators
Beyond the Callback: Yield Control with Javascript Generators
 
jQuery-1-Ajax
jQuery-1-AjaxjQuery-1-Ajax
jQuery-1-Ajax
 

Semelhante a Arquitetando seu app Android com Jetpack

OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
mfrancis
 

Semelhante a Arquitetando seu app Android com Jetpack (20)

Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com Jetpack
 
Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
 
Firebase for Apple Developers
Firebase for Apple DevelopersFirebase for Apple Developers
Firebase for Apple Developers
 
Firebase for Apple Developers - SwiftHeroes
Firebase for Apple Developers - SwiftHeroesFirebase for Apple Developers - SwiftHeroes
Firebase for Apple Developers - SwiftHeroes
 
Single Page Applications on JavaScript and ASP.NET MVC4
Single Page Applications on JavaScript and ASP.NET MVC4Single Page Applications on JavaScript and ASP.NET MVC4
Single Page Applications on JavaScript and ASP.NET MVC4
 
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
4시간만에 따라해보는 Windows 10 앱 개발 샘플코드
 
MVS: An angular MVC
MVS: An angular MVCMVS: An angular MVC
MVS: An angular MVC
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com Backbone
 
Backbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCBackbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVC
 
Getting Started with Combine And SwiftUI
Getting Started with Combine And SwiftUIGetting Started with Combine And SwiftUI
Getting Started with Combine And SwiftUI
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
 
The Django Book chapter 5 Models
The Django Book chapter 5 ModelsThe Django Book chapter 5 Models
The Django Book chapter 5 Models
 
SwiftUI and Combine All the Things
SwiftUI and Combine All the ThingsSwiftUI and Combine All the Things
SwiftUI and Combine All the Things
 
Getting Healthy with Magnolia, Blossom and Spring
Getting Healthy with Magnolia, Blossom and SpringGetting Healthy with Magnolia, Blossom and Spring
Getting Healthy with Magnolia, Blossom and Spring
 
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
OSGi and Spring Data for simple (Web) Application Development - Christian Bar...
 
OSGi and Spring Data for simple (Web) Application Development
OSGi and Spring Data  for simple (Web) Application DevelopmentOSGi and Spring Data  for simple (Web) Application Development
OSGi and Spring Data for simple (Web) Application Development
 
WordPress as the Backbone(.js)
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no Android
 
MVC Puree - Approaches to MVC with Umbraco
MVC Puree - Approaches to MVC with UmbracoMVC Puree - Approaches to MVC with Umbraco
MVC Puree - Approaches to MVC with Umbraco
 
Intro to IndexedDB (Beta)
Intro to IndexedDB (Beta)Intro to IndexedDB (Beta)
Intro to IndexedDB (Beta)
 

Mais de Nelson Glauber Leal

Mais de Nelson Glauber Leal (20)

Seu primeiro app Android e iOS com Compose Multiplatform
Seu primeiro app Android e iOS com Compose MultiplatformSeu primeiro app Android e iOS com Compose Multiplatform
Seu primeiro app Android e iOS com Compose Multiplatform
 
Desenvolvimento Moderno de Aplicações Android 2023
Desenvolvimento Moderno de Aplicações Android 2023Desenvolvimento Moderno de Aplicações Android 2023
Desenvolvimento Moderno de Aplicações Android 2023
 
Novidades incríveis do Android em 2023
Novidades incríveis do Android em 2023Novidades incríveis do Android em 2023
Novidades incríveis do Android em 2023
 
Novidades das Bibliotecas Jetpack do Android (2021)
Novidades das Bibliotecas Jetpack do Android (2021)Novidades das Bibliotecas Jetpack do Android (2021)
Novidades das Bibliotecas Jetpack do Android (2021)
 
Android Jetpack Compose - Turkey 2021
Android Jetpack Compose - Turkey 2021Android Jetpack Compose - Turkey 2021
Android Jetpack Compose - Turkey 2021
 
Jetpack Compose a new way to implement UI on Android
Jetpack Compose a new way to implement UI on AndroidJetpack Compose a new way to implement UI on Android
Jetpack Compose a new way to implement UI on Android
 
Jetpack Compose a nova forma de implementar UI no Android
Jetpack Compose a nova forma de implementar UI no AndroidJetpack Compose a nova forma de implementar UI no Android
Jetpack Compose a nova forma de implementar UI no Android
 
O que é preciso para ser um desenvolvedor Android
O que é preciso para ser um desenvolvedor AndroidO que é preciso para ser um desenvolvedor Android
O que é preciso para ser um desenvolvedor Android
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
 
Aplicações assíncronas no Android com Coroutines & Jetpack
Aplicações assíncronas no Android com Coroutines & JetpackAplicações assíncronas no Android com Coroutines & Jetpack
Aplicações assíncronas no Android com Coroutines & Jetpack
 
Desenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos AndroidDesenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos Android
 
Desenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos AndroidDesenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos Android
 
Turbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com KotlinTurbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com Kotlin
 
Tudo que você precisa saber sobre Constraint Layout
Tudo que você precisa saber sobre Constraint LayoutTudo que você precisa saber sobre Constraint Layout
Tudo que você precisa saber sobre Constraint Layout
 
Persistência de Dados no SQLite com Room
Persistência de Dados no SQLite com RoomPersistência de Dados no SQLite com Room
Persistência de Dados no SQLite com Room
 
The world of Android Animations
The world of Android AnimationsThe world of Android Animations
The world of Android Animations
 
Android Constraint Layout
Android Constraint LayoutAndroid Constraint Layout
Android Constraint Layout
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no Android
 
Android Wear DevFest Sudeste 2015
Android Wear DevFest Sudeste 2015Android Wear DevFest Sudeste 2015
Android Wear DevFest Sudeste 2015
 
Google Play Services Rocks!!!
Google Play Services Rocks!!!Google Play Services Rocks!!!
Google Play Services Rocks!!!
 

Último

Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Cara Menggugurkan Kandungan 087776558899
 

Último (6)

Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
Obat Penggugur Kandungan Di Apotik Kimia Farma (087776558899)
 
Leading Mobile App Development Companies in India (2).pdf
Leading Mobile App Development Companies in India (2).pdfLeading Mobile App Development Companies in India (2).pdf
Leading Mobile App Development Companies in India (2).pdf
 
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort ServiceBDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 71 Noida Escorts >༒8448380779 Escort Service
 
9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service
9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service
9999266834 Call Girls In Noida Sector 52 (Delhi) Call Girl Service
 
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCRFULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
FULL ENJOY - 9999218229 Call Girls in {Mahipalpur}| Delhi NCR
 
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost LoverPowerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
Powerful Love Spells in Arkansas, AR (310) 882-6330 Bring Back Lost Lover
 

Arquitetando seu app Android com Jetpack

  • 1. Arquitetando seu app Android com Jetpack Nelson Glauber @nglauber
  • 2. Porque devemos nos preocupar com arquitetura? ‣ Frameworks forçam o desenvolvedor a seguir o próprio framework e não os princípios da engenharia de software. ‣ Sua arquitetura deve deixar claro o propósito do sistema, não os frameworks utilizados. ‣ A lógica de negócio deve estar claramente separada e independente de framework.
  • 3. Arquitetura ‣ Regra Principal: não há regras. Mas existem princípios que devem ser seguidos. Lembra do S.O.L.I.D.? ‣ Promove a organização e o desacoplamento do código. ‣ Deve facilitar a manutenção e a adição de novas funcionalidades. ‣ Uma arquitetura deve ser testável! ‣ Ela incrementa a complexidade? Sim! Vale à pena? Com certeza! 😎 Mas deve ser de conhecimento de toda à equipe. Single Responsibility Principle Open-Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependency Inversion Principle
  • 4. MVP View IView Presenter Model User interaction Request DataUI Logic implements IPresenter implements
  • 5.
  • 6. MVVM View ViewModel Model Observes User interaction Data Request
  • 7.
  • 8. ordem das letras MVVM e MVP não faz sentido
  • 9.
  • 12.
  • 13.
  • 17. ‣ Módulo Kotlin ‣ Normalmente contém classes de dados e interfaces. ‣ Decisão importante aqui: esse app será reativo? 🤔 Data
  • 20. interface BooksRepository { fun loadBooks(): Flow<List<Book>> fun loadBook(bookId: String): Flow<Book> suspend fun saveBook(book: Book) suspend fun remove(book: Book) }
  • 22. Local ‣ Módulo Android ‣ Faz a implementação do repositório local ‣ Book do módulo data é diferente do Book deste módulo
  • 23. Room ‣ ORM (Object-Relational Mapping) para SQLite. ‣ Suporta live updates por meio de LiveData, RXJava (Observable/Flowable) e Coroutines (suspend/flow) Local
  • 24.
  • 25. @Entity @TypeConverters(MediaTypeConverter::class) data class Book( @PrimaryKey var id: String, var title: String = "", var author: String = "", var coverUrl: String = "", var pages: Int = 0, var year: Int = 0, @Embedded(prefix = "publisher_") var publisher: Publisher, var available: Boolean = false, var mediaType: MediaType = MediaType.PAPER, var rating: Float = 0f )
  • 26. @Dao interface BookDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun save(book: Book) @Delete suspend fun delete(vararg book: Book) @Query("SELECT * FROM Book WHERE title LIKE :title ORDER BY title") fun bookByTitle(title: String = "%"): Flow<List<Book>> @Query("SELECT * FROM Book WHERE id = :id") fun bookById(id: String): Flow<Book> }
  • 27. @Database(entities = [Book::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun bookDao(): BookDao companion object { private var instance: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { if (instance == null) { instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "booksDb") .build() } return instance as AppDatabase } } }
  • 28. class RoomRepository( db: AppDatabase, private val fileHelper: FileHelper ) : BooksRepository { private val bookDao = db.bookDao() override suspend fun saveBook(book: Book) { if (book.id.isBlank()) { book.id = UUID.randomUUID().toString() } return if (fileHelper.saveCover(book)) { bookDao.save(BookConverter.fromData(book)) } else { throw RuntimeException("Error saving book's cover.") } }
 ...
  • 29. class RoomRepository( db: AppDatabase, private val fileHelper: FileHelper ) : BooksRepository { private val bookDao = db.bookDao() override fun loadBooks(): Flow<List<Book>> { return bookDao.bookByTitle() .map { books -> books.map { book -> BookConverter.toData(book) } } } ...
  • 31. ‣ Módulo Kotlin ‣ Implementação da lógica de maneira abstrata Domain
  • 32. open class ListBooksUseCase( private val repository: BooksRepository ) { fun execute(): Flow<List<Book>> { return repository.loadBooks() } }
  • 33. open class SaveBookUseCase( private val repository: BooksRepository ) { suspend fun execute(params: Book) { return if (bookIsValid(params)) { repository.saveBook(params) } else { throw IllegalArgumentException("Book is not valid") } } private fun bookIsValid(book: Book): Boolean { return ( book.title.isNotBlank() && book.author.isNotBlank() && book.pages > 0 && book.year > 1900 && book.year <= Calendar.getInstance().get(Calendar.YEAR) ) } }
  • 35. Data Binding • Torna fácil a conexão entre a View e o Presenter/ViewModel. • Permite extender arquivos de layout com micro-expressões. • Muito útil em telas onde há input de dados. Presentation
  • 36.
  • 37. @Parcelize class Book : BaseObservable(), Parcelable { @Bindable var id: String = "" set(value) { field = value notifyPropertyChanged(BR.id) } @Bindable var title: String = "" set(value) { field = value notifyPropertyChanged(BR.title) } // demais atributos }
  • 38. <layout ...> <data> ... <import type="dominando.android.presentation.binding.MediaType" /> <variable name="book" type="dominando.android.presentation.binding.Book" /> <variable name="presenter" type="dominando.android.livros.BookFragment" /> </data> <androidx.constraintlayout.widget.ConstraintLayout ...> <ImageView android:src=“@{book.coverUrl}" ... android:onClick="@{presenter::clickTakePhoto}"/> <EditText android:text="@={book.title}" ... /> <EditText android:text="@={book.author}" ... /> <EditText android:text="@={book.pages}" ... /> <EditText android:text="@={book.year}" ... /> <Spinner ...> <CheckBox android:checked="@={book.available}" ... /> <RadioGroup ...> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.EBOOK}" ... /> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.PAPER}" ... /> </RadioGroup> <RatingBar android:rating="@={book.rating}" ... /> <Button android:onClick="@{presenter::clickSaveBook}" .../> </androidx.constraintlayout.widget.ConstraintLayout> </layout> res/layout/fragment_book_form.xml
  • 39. class BookFormFragment : BaseFragment() { private lateinit var binding: FragmentBookFormBinding override fun onCreateView ... { binding = DataBindingUtil.inflate( inflater, R.layout.fragment_book_form, container, false ) return binding.root } override fun onViewCreated ... { binding.book = viewModel.book binding.presenter = this } ...
  • 41. LiveData • LiveData stores observable data (Observable) and notify the observers (Observer) when the data changes allowing the UI to be updated. • LiveData is lifecycle-aware, which means that it only notify the observers if the Activity/Fragment are either in STARTED or RESUMED state. Presentation
  • 42. class BookDetailsViewModel(private val useCase: ViewBookDetailsUseCase) : ViewModel() { private val state: MutableLiveData<ViewState<BookBinding>> = MutableLiveData() fun getState(): LiveData<ViewState<BookBinding>> = state fun loadBook(id: String) { ... viewModelScope.launch { state.postValue(ViewState(ViewState.Status.LOADING)) try { useCase.execute(id).collect { book -> if (book != null) { val bookBinding = BookConverter.fromData(book) state.postValue(ViewState(ViewState.Status.SUCCESS, bookBinding)) } else { state.postValue( ViewState(ViewState.Status.ERROR, RuntimeException("Book not found")) ) } } } catch (e: Exception) { state.postValue(ViewState(ViewState.Status.ERROR, error = e)) } } }
  • 43. class BookDetailsFragment : BaseFragment() { private val viewModel: BookDetailsViewModel by viewModels { BookVmFactory(requireActivity().application) } … private fun init() { viewModel.getState().observe(viewLifecycleOwner, Observer { viewState -> when (viewState.status) { ViewState.Status.SUCCESS -> binding.book = viewState.data ViewState.Status.LOADING -> {} /* TODO */ ViewState.Status.ERROR -> {}/* TODO */ } }) val book = arguments?.getParcelable<Book>("book") book?.let { viewModel.loadBook(book.id) } }
  • 44. Presentation open class LiveEvent<out T>(private val content: T) { var hasBeenConsumed = false private set fun consumeEvent(): T? { return if (hasBeenConsumed) { null } else { hasBeenConsumed = true content } } fun peekContent(): T = content }
  • 45. class BookFormViewModel( private val useCase: SaveBookUseCase ) : ViewModel(), LifecycleObserver { private val state: MutableLiveData<LiveEvent<ViewState<Unit>>> = MutableLiveData() fun getState(): LiveData<LiveEvent<ViewState<Unit>>> = state fun saveBook(book: BookBinding) { state.postValue(LiveEvent(ViewState(ViewState.Status.LOADING))) viewModelScope.launch { try { withContext(Dispatchers.IO) { useCase.execute(BookConverter.toData(book)) } state.postValue(LiveEvent(ViewState(ViewState.Status.SUCCESS))) } catch (e: Exception) { state.postValue(LiveEvent(ViewState(ViewState.Status.ERROR, error = e))) } } }
  • 46. class BookFormFragment : BaseFragment() { ... private fun init() { viewModel.getState().observe(this, Observer { event -> event?.peekContent()?.let { state -> when (state.status) { ViewState.Status.LOADING -> {...} ViewState.Status.SUCCESS -> {...} ViewState.Status.ERROR -> { ... event.consumeEvent() } } } }) }
  • 47. Lifecycle ‣ Lifecycle is an object which defines a life cycle. ‣ LifecycleOwner is the interface to be implemented by objects with a life cycle ‣ Activity and Fragment implements LifecycleOwner and has Lifecycle. ‣ LifecycleObserver is the interface to be implemented by who wants to observe a LifecycleOwner.
  • 48. class BookListViewModel( private val loadBooksUseCase: ListBooksUseCase, ... ) : ViewModel(), LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun loadBooks() { if (state.value == null) { viewModelScope.launch { state.postValue(ViewState(ViewState.Status.LOADING)) try { loadBooksUseCase.execute() .collect { books -> val booksBinding = books.map { book -> BookConverter.fromData(book) } state.postValue( ViewState(ViewState.Status.SUCCESS, booksBinding) ) } } catch (e: Exception) { state.postValue(ViewState(ViewState.Status.ERROR, error = e)) } } } }
  • 49. class BookListFragment : BaseFragment() { private val viewModel: BookListViewModel private fun init() { ... lifecycle.addObserver(viewModel) } ...
  • 51. UI (app) Navigation API • Introduz o conceito de “Single Activity”. • Centraliza a lógica de navegação da aplicação. • Permite a conexão direta com componentes de UI, tais como: ActionBar, Button, Menu, BottomNav, … • Simplifica a passagem e atribuição de parâmetros (sem mais método newInstance).
  • 52.
  • 53. <navigation ... android:id="@+id/main_graph" app:startDestination="@id/signInFragment"> <fragment android:id="@+id/listBooks" android:name="dominando.android.livros.BookListFragment" android:label="@string/app_name" tools:layout="@layout/fragment_book_list"> <action android:id="@+id/action_list_to_form" app:destination="@id/formBook" ... /> ... </fragment> <fragment android:id="@+id/bookDetails" android:name="dominando.android.livros.BookDetailsFragment" android:label="@string/app_name" tools:layout="@layout/fragment_book_details"> ... <argument android:name="book" app:argType="dominando.android.presentation.binding.Book" app:nullable="true" /> </fragment>
  • 55. class BookActivity : AppCompatActivity() { private val navController: NavController by lazy { Navigation.findNavController(this, R.id.navHost) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_book) NavigationUI.setupActionBarWithNavController(this, navController) } override fun onSupportNavigateUp(): Boolean { return navController.navigateUp() } }
  • 56. // BookListFragment val args = Bundle().apply { putParcelable("book", book) } navController.navigate(R.id.action_list_to_details, args) // BookDetailsFragment val book = arguments?.getParcelable<Book>("book") binding.book = book
  • 58. Bonus Topics • Organização do projeto: por pacote ou por feature? • Injeção de Dependência: Dagger x Koin • Testes (JUnit, Espresso, MockK, Hamcrest, …) • Lint & KtLint • Git Hooks
  • 60. • Você não precisa usar tudo isso na sua aplicação • Na verdade, você não precisa usar nenhum! • O importante é saber QUANDO USAR is QUANDO NÃO USAR 😉 • É essencial conhecer esses tópicos, seus prós e contras e usá-los adequadamente 💡
  • 61. References • Clean Architecture
 https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean- architecture.html • Android Architecture Components
 https://developer.android.com/topic/libraries/architecture/ • LiveData beyond the ViewModel
 https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel- reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7 • Android Architecture (Five Agency)
 https://five.agency/android-architecture-part-1-every-new-beginning-is-hard/ • Joe Birch (@hitherejoe) Course at Caster.io
 https://caster.io/courses/android-clean-architecture