SlideShare a Scribd company logo
1 of 101
Download to read offline
Arquitetando seu app
Android com Jetpack
Nelson Glauber
@nglauber
Porque devemos nos
preocupar com arquitetura?
‣ Diversos tipos de negócios estão sendo feitos pelo
celular. Muitos deles são mobile first.
‣ A complexidade dos aplicativos está cada dia maior.
‣ Por isso o código tem que ser robusto, testável e
deve facilitar a manutenção e a adição de
funcionalidades.
‣ Atualmente existem diversos frameworks para ajudar
nessas tarefas, mas eles são suficientes?
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.
‣ É preciso ter uma arquitetura que deixe 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 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

Model View Presenter
View
IView
Presenter Model
User interaction Request
DataUI Logic
implements
IPresenter
implements
VIEW
Fragment
PRESENTER
Activity
REPOSITORY
Remote
Data Source
Local
Data Source
MVVM
Model View View-Model
View ViewModel Model
Observes
User interaction
Data
Request
VIEW
Fragment
VIEWMODEL
Activity
REPOSITORY
Remote
Data Source
Local
Data Source
Data

Binding
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
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)
}
interface BooksRepository {
fun loadBooks(): Flow<List<Book>>
fun loadBook(bookId: String): Flow<Book>
suspend fun saveBook(book: Book)
suspend fun remove(book: Book)
}
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
Data - Local
• Módulo Android
• Faz a implementação do
repositório local.
• A classe Book deste
módulo é diferente da
classe Book do módulo
data.
Local
Room
• ORM (Object-Relational
Mapping) para SQLite.
• Suporta live updates por
meio de:
• LiveData
• RXJava 

(Observable/Flowable)
• Coroutines 

(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
)
@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
}
}
}
@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
}
}
}
@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
}
}
}
@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 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 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
Domain
• Módulo Kotlin
• Implementação da lógica
de negócio 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)
)
}
}
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)
)
}
}
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)
)
}
}
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 ligação
entre a View e o
ViewModel (ou
Presenter).
• Permite estender
arquivos de layout com
micro-expressões.
• Particularmente ú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
}
@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
}
@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=“formView" type="dominando.android.livros.BookFragment" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<ImageView android:src="@{book.coverUrl}" ...
android:onClick="@{formView::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="@{formView::clickSaveBook}" .../>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
res/layout/fragment_book_form.xml
<layout ...>
<data> ...
<import type="dominando.android.presentation.binding.MediaType" />
<variable name="book" type="dominando.android.presentation.binding.Book" />
<variable name="formView" type="dominando.android.livros.BookFragment" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<ImageView android:src="@{book.coverUrl}" ...
android:onClick="@{formView::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="@{formView::clickSaveBook}" .../>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
res/layout/fragment_book_form.xml
<layout ...>
<data> ...
<import type="dominando.android.presentation.binding.MediaType" />
<variable name="book" type="dominando.android.presentation.binding.Book" />
<variable name="formView" type="dominando.android.livros.BookFragment" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<ImageView android:src="@{book.coverUrl}" ...
android:onClick="@{formView::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="@{formView::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.formView = this
}
...
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.formView = this
}
...
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.formView = this
}
...
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.formView = this
}
...
ViewModel Presentation
• Mantém a lógica de
apresentação.
• Recebe as ações da
view.
• Fornece dados
observáveis para view.
• Sobrevive às mudanças
de configuração.
ViewModel Presentation
LiveData
• Armazena dados observáveis
(Observable) e notifica os
observadores (Observer)
quando esses dados são
modificados permitindo que
UI seja atualizada.
• LiveData é lifecycle-aware,
o que significa que os
observadores só serão
notificados se a Activity/
Fragment estiver no estado
de STARTED ou RESUMED.
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 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 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 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 -> {} /* Exibir Loading */
ViewState.Status.ERROR -> {} /* Exibir Erro */
}
})
val book = arguments?.getParcelable<Book>("book")
book?.let {
viewModel.loadBook(book.id)
}
}
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 -> {} /* Exibir Loading */
ViewState.Status.ERROR -> {} /* Exibir Erro */
}
})
val book = arguments?.getParcelable<Book>("book")
book?.let {
viewModel.loadBook(book.id)
}
}
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 -> {} /* Exibir Loading */
ViewState.Status.ERROR -> {} /* Exibir Erro */
}
})
val book = arguments?.getParcelable<Book>("book")
book?.let {
viewModel.loadBook(book.id)
}
}
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 -> {} /* Exibir Loading */
ViewState.Status.ERROR -> {} /* Exibir Erro */
}
})
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 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 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 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()
}
}
}
})
}
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 é um objeto que define um ciclo de vida.
• LifecycleOwner é a interface a ser implementada por
objetos que que possuem um ciclo de vida.
• Activity e Fragment implementam LifecycleOwner e
têm Lifecycle.
• LifecycleObserver é a interface a ser implementada
pela classe que deseja observar o ciclo de vida de
um LifecycleOwner.
class BookListViewModel(
private val loadBooksUseCase: ListBooksUseCase, ...
) : ViewModel(), LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun loadBooks() {
...
}
class BookListFragment : BaseFragment() {
private val viewModel: BookListViewModel
private fun init() {
...
lifecycle.addObserver(viewModel)
}
...
UI (app)
Presentation
Domain
Remote Local
Data
Navigation API
• Introduz o paradigma de Single
Activity.
• Representação visual do fluxo de
telas.
• Centraliza a lógica de
navegação da aplicação.
• Permite a conexão direta com
componentes de UI como:
ActionBar, Button, Menu,
BottomNav, …
• Simplifica a passagem e
atribuição de parâmetros.
UI (app)
<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>
<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>
<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>
<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>
<FrameLayout ...>
<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" />
</FrameLayout>
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()
}
}
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()
}
}
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()
}
}
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
WorkManager
• WorkManager facilita o agendamento de tarefas assíncronas e que
devem ser executadas mesmo que o aplicativo não esteja em
execução ou o dispositivo seja reiniciado.
• O agendamento pode ser único ou periódico (15 min. de intervalo)
• Uso de parâmetros de entrada e saída.
• Suporte a execuções em cadeia (chaining) (beginWith & then)
• Observar o estado do work via LiveData.
• Permite o uso de constraints (nível de bateria, tipo de rede, storage, …)
• Possui políticas de retries & backoff.
https://android-developers.googleblog.com/2018/10/modern-background-execution-in-android.html
Paging
• Carregar dados de forma paginada e sob demanda
reduz o uso da largura de banda da rede e dos
recursos do sistema.
• A Paging Library permite carregar os dados
gradualmente para listagens finitas ou “infinitas”.
• Integração com RecyclerView e LiveData
https://www.youtube.com/watch?v=QVMqCRs0BNA
#
#
🧐 #
#
#
#
🧐 #
🧐 #
Bonus Topics
• Organização do projeto: por pacote ou por feature?
• Injeção de Dependência: Dagger x Koin (ou Kodein)?
• Testes (JUnit, Robolectric, Espresso, MockK,
MockWebServer, …)
• Lint & KtLint
• Git Hooks
• Como está o mercado?
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 é 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

More Related Content

What's hot

Differential Sync and JSON Patch @ SpringOne2GX 2014
Differential Sync and JSON Patch @ SpringOne2GX 2014Differential Sync and JSON Patch @ SpringOne2GX 2014
Differential Sync and JSON Patch @ SpringOne2GX 2014Brian Cavalier
 
Reenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWSReenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWSWO Community
 
Benchx: An XQuery benchmarking web application
Benchx: An XQuery benchmarking web application Benchx: An XQuery benchmarking web application
Benchx: An XQuery benchmarking web application Andy Bunce
 
JavaScript!
JavaScript!JavaScript!
JavaScript!RTigger
 
Getting started with MongoDB and Scala - Open Source Bridge 2012
Getting started with MongoDB and Scala - Open Source Bridge 2012Getting started with MongoDB and Scala - Open Source Bridge 2012
Getting started with MongoDB and Scala - Open Source Bridge 2012sullis
 
Barcamp Auckland Rails3 presentation
Barcamp Auckland Rails3 presentationBarcamp Auckland Rails3 presentation
Barcamp Auckland Rails3 presentationSociable
 
Introducing RaveJS: Spring Boot concepts for JavaScript applications
Introducing RaveJS: Spring Boot concepts for JavaScript applicationsIntroducing RaveJS: Spring Boot concepts for JavaScript applications
Introducing RaveJS: Spring Boot concepts for JavaScript applicationsJohn Hann
 
Converting a Rails application to Node.js
Converting a Rails application to Node.jsConverting a Rails application to Node.js
Converting a Rails application to Node.jsMatt Sergeant
 
Elasticsearch for SQL Users
Elasticsearch for SQL UsersElasticsearch for SQL Users
Elasticsearch for SQL UsersAll Things Open
 
Building data flows with Celery and SQLAlchemy
Building data flows with Celery and SQLAlchemyBuilding data flows with Celery and SQLAlchemy
Building data flows with Celery and SQLAlchemyRoger Barnes
 
Advancing JavaScript with Libraries (Yahoo Tech Talk)
Advancing JavaScript with Libraries (Yahoo Tech Talk)Advancing JavaScript with Libraries (Yahoo Tech Talk)
Advancing JavaScript with Libraries (Yahoo Tech Talk)jeresig
 
Geodaten & Drupal 7
Geodaten & Drupal 7Geodaten & Drupal 7
Geodaten & Drupal 7Michael Milz
 
2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)
2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)
2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)Johannes Hoppe
 
MongoDB & NoSQL 101
 MongoDB & NoSQL 101 MongoDB & NoSQL 101
MongoDB & NoSQL 101Jollen Chen
 
iOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core DataiOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core DataChris Mar
 
HTML5: Building the Next Generation of Web Applications
HTML5: Building the Next Generation of Web ApplicationsHTML5: Building the Next Generation of Web Applications
HTML5: Building the Next Generation of Web ApplicationsChrome Developer Relations
 
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald PehlGWTcon
 

What's hot (20)

Differential Sync and JSON Patch @ SpringOne2GX 2014
Differential Sync and JSON Patch @ SpringOne2GX 2014Differential Sync and JSON Patch @ SpringOne2GX 2014
Differential Sync and JSON Patch @ SpringOne2GX 2014
 
XQuery Rocks
XQuery RocksXQuery Rocks
XQuery Rocks
 
Reenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWSReenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWS
 
Benchx: An XQuery benchmarking web application
Benchx: An XQuery benchmarking web application Benchx: An XQuery benchmarking web application
Benchx: An XQuery benchmarking web application
 
jQuery Objects
jQuery ObjectsjQuery Objects
jQuery Objects
 
JavaScript!
JavaScript!JavaScript!
JavaScript!
 
Getting started with MongoDB and Scala - Open Source Bridge 2012
Getting started with MongoDB and Scala - Open Source Bridge 2012Getting started with MongoDB and Scala - Open Source Bridge 2012
Getting started with MongoDB and Scala - Open Source Bridge 2012
 
Barcamp Auckland Rails3 presentation
Barcamp Auckland Rails3 presentationBarcamp Auckland Rails3 presentation
Barcamp Auckland Rails3 presentation
 
Introducing RaveJS: Spring Boot concepts for JavaScript applications
Introducing RaveJS: Spring Boot concepts for JavaScript applicationsIntroducing RaveJS: Spring Boot concepts for JavaScript applications
Introducing RaveJS: Spring Boot concepts for JavaScript applications
 
Converting a Rails application to Node.js
Converting a Rails application to Node.jsConverting a Rails application to Node.js
Converting a Rails application to Node.js
 
Elasticsearch for SQL Users
Elasticsearch for SQL UsersElasticsearch for SQL Users
Elasticsearch for SQL Users
 
Building data flows with Celery and SQLAlchemy
Building data flows with Celery and SQLAlchemyBuilding data flows with Celery and SQLAlchemy
Building data flows with Celery and SQLAlchemy
 
Advancing JavaScript with Libraries (Yahoo Tech Talk)
Advancing JavaScript with Libraries (Yahoo Tech Talk)Advancing JavaScript with Libraries (Yahoo Tech Talk)
Advancing JavaScript with Libraries (Yahoo Tech Talk)
 
Geodaten & Drupal 7
Geodaten & Drupal 7Geodaten & Drupal 7
Geodaten & Drupal 7
 
Bonjour, iCloud
Bonjour, iCloudBonjour, iCloud
Bonjour, iCloud
 
2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)
2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)
2012-08-29 - NoSQL Bootcamp (Redis, RavenDB & MongoDB für .NET Entwickler)
 
MongoDB & NoSQL 101
 MongoDB & NoSQL 101 MongoDB & NoSQL 101
MongoDB & NoSQL 101
 
iOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core DataiOSDevCamp 2011 Core Data
iOSDevCamp 2011 Core Data
 
HTML5: Building the Next Generation of Web Applications
HTML5: Building the Next Generation of Web ApplicationsHTML5: Building the Next Generation of Web Applications
HTML5: Building the Next Generation of Web Applications
 
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
 

Similar to Arquitetando seu app Android com Jetpack

Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackNelson Glauber Leal
 
Google App Engine With Java And Groovy
Google App Engine With Java And GroovyGoogle App Engine With Java And Groovy
Google App Engine With Java And GroovyKen Kousen
 
Apache Calcite (a tutorial given at BOSS '21)
Apache Calcite (a tutorial given at BOSS '21)Apache Calcite (a tutorial given at BOSS '21)
Apache Calcite (a tutorial given at BOSS '21)Julian Hyde
 
Mvc - Model: the great forgotten
Mvc - Model: the great forgottenMvc - Model: the great forgotten
Mvc - Model: the great forgottenDavid Rodenas
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to DjangoJames Casey
 
The Django Book chapter 5 Models
The Django Book chapter 5 ModelsThe Django Book chapter 5 Models
The Django Book chapter 5 ModelsVincent Chien
 
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 MVC4Yuriy Shapovalov
 
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 & JetpackNelson Glauber Leal
 
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 & JetpackNelson Glauber Leal
 
Introduction tomongodb
Introduction tomongodbIntroduction tomongodb
Introduction tomongodbLee Theobald
 
Firebase for Apple Developers
Firebase for Apple DevelopersFirebase for Apple Developers
Firebase for Apple DevelopersPeter Friese
 
Aplicações Assíncronas no Android com Coroutines e Jetpack
Aplicações Assíncronas no Android com Coroutines e JetpackAplicações Assíncronas no Android com Coroutines e Jetpack
Aplicações Assíncronas no Android com Coroutines e JetpackNelson Glauber Leal
 
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
 
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 DevelopmentChristian Baranowski
 
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 & JetpackNelson Glauber Leal
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...Corley S.r.l.
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...Walter Dal Mut
 
An Introduction To NoSQL & MongoDB
An Introduction To NoSQL & MongoDBAn Introduction To NoSQL & MongoDB
An Introduction To NoSQL & MongoDBLee Theobald
 
Elasticsearch And Ruby [RuPy2012]
Elasticsearch And Ruby [RuPy2012]Elasticsearch And Ruby [RuPy2012]
Elasticsearch And Ruby [RuPy2012]Karel Minarik
 

Similar to 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
 
Google App Engine With Java And Groovy
Google App Engine With Java And GroovyGoogle App Engine With Java And Groovy
Google App Engine With Java And Groovy
 
Apache Calcite (a tutorial given at BOSS '21)
Apache Calcite (a tutorial given at BOSS '21)Apache Calcite (a tutorial given at BOSS '21)
Apache Calcite (a tutorial given at BOSS '21)
 
"Javascript" por Tiago Rodrigues
"Javascript" por Tiago Rodrigues"Javascript" por Tiago Rodrigues
"Javascript" por Tiago Rodrigues
 
Mvc - Model: the great forgotten
Mvc - Model: the great forgottenMvc - Model: the great forgotten
Mvc - Model: the great forgotten
 
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
 
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
 
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
 
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
 
Introduction tomongodb
Introduction tomongodbIntroduction tomongodb
Introduction tomongodb
 
Firebase for Apple Developers
Firebase for Apple DevelopersFirebase for Apple Developers
Firebase for Apple Developers
 
Aplicações Assíncronas no Android com Coroutines e Jetpack
Aplicações Assíncronas no Android com Coroutines e JetpackAplicações Assíncronas no Android com Coroutines e Jetpack
Aplicações Assíncronas no Android com Coroutines e 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...
 
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
 
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
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...
 
An Introduction To NoSQL & MongoDB
An Introduction To NoSQL & MongoDBAn Introduction To NoSQL & MongoDB
An Introduction To NoSQL & MongoDB
 
Elasticsearch And Ruby [RuPy2012]
Elasticsearch And Ruby [RuPy2012]Elasticsearch And Ruby [RuPy2012]
Elasticsearch And Ruby [RuPy2012]
 

More from Nelson Glauber Leal

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 MultiplatformNelson Glauber Leal
 
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 2023Nelson Glauber Leal
 
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 2023Nelson Glauber Leal
 
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)Nelson Glauber Leal
 
Android Jetpack Compose - Turkey 2021
Android Jetpack Compose - Turkey 2021Android Jetpack Compose - Turkey 2021
Android Jetpack Compose - Turkey 2021Nelson Glauber Leal
 
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 AndroidNelson Glauber Leal
 
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 AndroidNelson Glauber Leal
 
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 AndroidNelson Glauber Leal
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard LibraryNelson Glauber Leal
 
Introdução ao Desenvolvimento Android com Kotlin
Introdução ao Desenvolvimento Android com KotlinIntrodução ao Desenvolvimento Android com Kotlin
Introdução ao Desenvolvimento Android com KotlinNelson Glauber Leal
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using RoomNelson Glauber Leal
 
Desenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos AndroidDesenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos AndroidNelson Glauber Leal
 
Desenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos AndroidDesenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos AndroidNelson Glauber Leal
 
Turbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com KotlinTurbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com KotlinNelson Glauber Leal
 
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 LayoutNelson Glauber Leal
 
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 RoomNelson Glauber Leal
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no AndroidNelson Glauber Leal
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no AndroidNelson Glauber Leal
 

More from 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
 
Introdução ao Desenvolvimento Android com Kotlin
Introdução ao Desenvolvimento Android com KotlinIntrodução ao Desenvolvimento Android com Kotlin
Introdução ao Desenvolvimento Android com Kotlin
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
 
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
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no Android
 

Recently uploaded

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsJhone kinadey
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Clustering techniques data mining book ....
Clustering techniques data mining book ....Clustering techniques data mining book ....
Clustering techniques data mining book ....ShaimaaMohamedGalal
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 

Recently uploaded (20)

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Clustering techniques data mining book ....
Clustering techniques data mining book ....Clustering techniques data mining book ....
Clustering techniques data mining book ....
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 

Arquitetando seu app Android com Jetpack

  • 1. Arquitetando seu app Android com Jetpack Nelson Glauber @nglauber
  • 2. Porque devemos nos preocupar com arquitetura? ‣ Diversos tipos de negócios estão sendo feitos pelo celular. Muitos deles são mobile first. ‣ A complexidade dos aplicativos está cada dia maior. ‣ Por isso o código tem que ser robusto, testável e deve facilitar a manutenção e a adição de funcionalidades. ‣ Atualmente existem diversos frameworks para ajudar nessas tarefas, mas eles são suficientes?
  • 3. 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. ‣ É preciso ter uma arquitetura que deixe 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.
  • 4. 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 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
  • 5. MVP
 Model View Presenter View IView Presenter Model User interaction Request DataUI Logic implements IPresenter implements
  • 7. MVVM Model View View-Model View ViewModel Model Observes User interaction Data Request
  • 9.
  • 12.
  • 13.
  • 17. Data • 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) }
  • 21. interface BooksRepository { fun loadBooks(): Flow<List<Book>> fun loadBook(bookId: String): Flow<Book> suspend fun saveBook(book: Book) suspend fun remove(book: Book) }
  • 22. interface BooksRepository { fun loadBooks(): Flow<List<Book>> fun loadBook(bookId: String): Flow<Book> suspend fun saveBook(book: Book) suspend fun remove(book: Book) }
  • 24. Data - Local • Módulo Android • Faz a implementação do repositório local. • A classe Book deste módulo é diferente da classe Book do módulo data. Local
  • 25. Room • ORM (Object-Relational Mapping) para SQLite. • Suporta live updates por meio de: • LiveData • RXJava 
 (Observable/Flowable) • Coroutines 
 (flow) Local
  • 26.
  • 27. @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 )
  • 28. @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 )
  • 29. @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> }
  • 30. @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 } } }
  • 31. @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 } } }
  • 32. @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 } } }
  • 33. @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 } } }
  • 34. 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.") } }
 ...
  • 35. 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.") } }
 ...
  • 36. 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.") } }
 ...
  • 37. 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) } } } ...
  • 39. Domain • Módulo Kotlin • Implementação da lógica de negócio maneira abstrata. Domain
  • 40. open class ListBooksUseCase( private val repository: BooksRepository ) { fun execute(): Flow<List<Book>> { return repository.loadBooks() } }
  • 41. 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) ) } }
  • 42. 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) ) } }
  • 43. 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) ) } }
  • 44. 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) ) } }
  • 46. Data Binding • Torna fácil a ligação entre a View e o ViewModel (ou Presenter). • Permite estender arquivos de layout com micro-expressões. • Particularmente útil em telas onde há input de dados. Presentation
  • 47.
  • 48. @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 }
  • 49. @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 }
  • 50. @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 }
  • 51. <layout ...> <data> ... <import type="dominando.android.presentation.binding.MediaType" /> <variable name="book" type="dominando.android.presentation.binding.Book" /> <variable name=“formView" type="dominando.android.livros.BookFragment" /> </data> <androidx.constraintlayout.widget.ConstraintLayout ...> <ImageView android:src="@{book.coverUrl}" ... android:onClick="@{formView::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="@{formView::clickSaveBook}" .../> </androidx.constraintlayout.widget.ConstraintLayout> </layout> res/layout/fragment_book_form.xml
  • 52. <layout ...> <data> ... <import type="dominando.android.presentation.binding.MediaType" /> <variable name="book" type="dominando.android.presentation.binding.Book" /> <variable name="formView" type="dominando.android.livros.BookFragment" /> </data> <androidx.constraintlayout.widget.ConstraintLayout ...> <ImageView android:src="@{book.coverUrl}" ... android:onClick="@{formView::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="@{formView::clickSaveBook}" .../> </androidx.constraintlayout.widget.ConstraintLayout> </layout> res/layout/fragment_book_form.xml
  • 53. <layout ...> <data> ... <import type="dominando.android.presentation.binding.MediaType" /> <variable name="book" type="dominando.android.presentation.binding.Book" /> <variable name="formView" type="dominando.android.livros.BookFragment" /> </data> <androidx.constraintlayout.widget.ConstraintLayout ...> <ImageView android:src="@{book.coverUrl}" ... android:onClick="@{formView::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="@{formView::clickSaveBook}" .../> </androidx.constraintlayout.widget.ConstraintLayout> </layout> res/layout/fragment_book_form.xml
  • 54. 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.formView = this } ...
  • 55. 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.formView = this } ...
  • 56. 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.formView = this } ...
  • 57. 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.formView = this } ...
  • 58. ViewModel Presentation • Mantém a lógica de apresentação. • Recebe as ações da view. • Fornece dados observáveis para view. • Sobrevive às mudanças de configuração.
  • 60. LiveData • Armazena dados observáveis (Observable) e notifica os observadores (Observer) quando esses dados são modificados permitindo que UI seja atualizada. • LiveData é lifecycle-aware, o que significa que os observadores só serão notificados se a Activity/ Fragment estiver no estado de STARTED ou RESUMED. Presentation
  • 61. 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)) } } }
  • 62. 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)) } } }
  • 63. 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)) } } }
  • 64. 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)) } } }
  • 65. 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 -> {} /* Exibir Loading */ ViewState.Status.ERROR -> {} /* Exibir Erro */ } }) val book = arguments?.getParcelable<Book>("book") book?.let { viewModel.loadBook(book.id) } }
  • 66. 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 -> {} /* Exibir Loading */ ViewState.Status.ERROR -> {} /* Exibir Erro */ } }) val book = arguments?.getParcelable<Book>("book") book?.let { viewModel.loadBook(book.id) } }
  • 67. 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 -> {} /* Exibir Loading */ ViewState.Status.ERROR -> {} /* Exibir Erro */ } }) val book = arguments?.getParcelable<Book>("book") book?.let { viewModel.loadBook(book.id) } }
  • 68. 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 -> {} /* Exibir Loading */ ViewState.Status.ERROR -> {} /* Exibir Erro */ } }) val book = arguments?.getParcelable<Book>("book") book?.let { viewModel.loadBook(book.id) } }
  • 69. 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 }
  • 70. 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))) } } }
  • 71. 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))) } } }
  • 72. 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))) } } }
  • 73. 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))) } } }
  • 74. 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() } } } }) }
  • 75. 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() } } } }) }
  • 76. Lifecycle • Lifecycle é um objeto que define um ciclo de vida. • LifecycleOwner é a interface a ser implementada por objetos que que possuem um ciclo de vida. • Activity e Fragment implementam LifecycleOwner e têm Lifecycle. • LifecycleObserver é a interface a ser implementada pela classe que deseja observar o ciclo de vida de um LifecycleOwner.
  • 77. class BookListViewModel( private val loadBooksUseCase: ListBooksUseCase, ... ) : ViewModel(), LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun loadBooks() { ... }
  • 78. class BookListFragment : BaseFragment() { private val viewModel: BookListViewModel private fun init() { ... lifecycle.addObserver(viewModel) } ...
  • 80. Navigation API • Introduz o paradigma de Single Activity. • Representação visual do fluxo de telas. • Centraliza a lógica de navegação da aplicação. • Permite a conexão direta com componentes de UI como: ActionBar, Button, Menu, BottomNav, … • Simplifica a passagem e atribuição de parâmetros. UI (app)
  • 81.
  • 82. <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>
  • 83. <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>
  • 84. <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>
  • 85. <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>
  • 87. 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() } }
  • 88. 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() } }
  • 89. 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() } }
  • 90. 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() } }
  • 91. // 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
  • 92. WorkManager • WorkManager facilita o agendamento de tarefas assíncronas e que devem ser executadas mesmo que o aplicativo não esteja em execução ou o dispositivo seja reiniciado. • O agendamento pode ser único ou periódico (15 min. de intervalo) • Uso de parâmetros de entrada e saída. • Suporte a execuções em cadeia (chaining) (beginWith & then) • Observar o estado do work via LiveData. • Permite o uso de constraints (nível de bateria, tipo de rede, storage, …) • Possui políticas de retries & backoff.
  • 94. Paging • Carregar dados de forma paginada e sob demanda reduz o uso da largura de banda da rede e dos recursos do sistema. • A Paging Library permite carregar os dados gradualmente para listagens finitas ou “infinitas”. • Integração com RecyclerView e LiveData
  • 97. Bonus Topics • Organização do projeto: por pacote ou por feature? • Injeção de Dependência: Dagger x Koin (ou Kodein)? • Testes (JUnit, Robolectric, Espresso, MockK, MockWebServer, …) • Lint & KtLint • Git Hooks • Como está o mercado?
  • 99. • Você não precisa usar tudo isso na sua aplicação • Na verdade, você não precisa usar nenhum! • O importante é saber QUANDO USAR é QUANDO NÃO USAR 😉 • É essencial conhecer esses tópicos, seus prós e contras e usá-los adequadamente 💡
  • 100. 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