SlideShare a Scribd company logo
Arquitetando seu aplicativo
Android com o Jetpack
+Nelson Glauber
@nglauber

www.nglauber.com.br

youtube.com/nglauber
Porque pensar na
arquitetura?
‣ Frameworks raramente forçam o desenvolvedor a seguir
princípios.
‣ É fácil um desenvolvedor iniciante sem o devido
acompanhamento criar um app ruim.
‣ Tecnologia e frameworks ocultam o propósito do
aplicativo.
‣ “Sua arquitetura deve gritar o propósito do aplicativo”. A
lógica de negócio deve ser claramente separada e
independente de framework.
Arquitetura
‣ Regra: Não há regras. Mas existem princípios que devem
ser respeitados. Lembra do S.O.L.I.D.?
‣ Promove a organização e o desacoplamento do código.
‣ Devem facilitar a manutenção e adição de novas
funcionalidades.
‣ Devem ser testáveis.
‣ Deixam o código mais complexo? Sim! Mas vale à pena. 😎
MVP
View
IView
Presenter Model
Interação 

do usuário
Request
DadosLógica de UI
implements
IPresenter
implements
MVVM
View ViewModel Model
Observes
Interação 

do usuário
Dados
Request
https://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
androidx.*
UI (app)
Presentation
Domain
Remote Local
Data
UI (app)
Presentation
Domain
Remote Local
Data
‣ Módulo Kotlin
‣ Normalmente classes puras ou
interfaces
‣ Ser ou não ser reativo aqui… 🤔
Data
RX Java
import dominando.android.data.model.Book
import io.reactivex.Completable
import io.reactivex.Flowable
interface BooksRepository {
fun saveBook(book: Book): Completable
fun loadBooks(): Flowable<List<Book>>
fun loadBook(bookId: String): Flowable<Book>
fun remove(book: Book): Completable
}
UI (app)
Presentation
Domain
Remote Local
Data
Local
Room
‣ ORM para Android sobre o SQLite.
‣ Suporta atualização automática da UI
com LiveData ou RXJava
Local
@Entity
@TypeConverters(MediaTypeConverter::class)
data class Book(
@PrimaryKey
var id: String,
var title: String = "",
var author: String = "",
var coverUrl: String = "",
var pages: Int = 0,
var year: Int = 0,
@Embedded(prefix = "publisher_")
var publisher: Publisher,
var available: Boolean = false,
var mediaType: MediaType = MediaType.PAPER,
var rating: Float = 0f
)
@Dao
interface BookDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun save(book: Book): Completable
@Delete
fun delete(vararg book: Book): Completable
@Query("SELECT * FROM Book WHERE title LIKE :title ORDER BY title")
fun bookByTitle(title: String = "%"): Flowable<List<Book>>
@Query("SELECT * FROM Book WHERE id = :id")
fun bookById(id: String): Flowable<Book>
}
@Database(entities = [Book::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun bookDao(): BookDao
companion object {
private var instance: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"booksDb")
.build()
}
return instance as AppDatabase
}
}
}
class RoomRepository(db: AppDatabase,
private val fileHelper: FileHelper) : BooksRepository {
private val bookDao = db.bookDao()
override fun saveBook(book: Book): Completable {
if (book.id.isBlank()) {
book.id = UUID.randomUUID().toString()
}
return bookDao.save(BookConverter.fromData(book))
}
override fun loadBooks(): Flowable<List<Book>> {
return bookDao.bookByTitle()
.map { books ->
books.map { book ->
BookConverter.toData(book)
}
}
}
...
UI (app)
Presentation
Domain
Remote Local
Data
‣ Módulo Kotlin
‣ Abstração da lógica de negócio
‣ A lógica deve ser implementada
aqui de forma abstrata.
Domain
open class ListBooksUseCase(
private val repository: BooksRepository,
postExecutionThread: PostExecutionThread
) : FlowableUseCase<List<Book>, Unit>(postExecutionThread) {
override fun buildUseCaseFlowable(params: Unit?)
: Flowable<List<Book>> {
return repository.loadBooks()
}
}
open class SaveBookUseCase(
private val repository: BooksRepository,
postExecutionThread: PostExecutionThread
): CompletableUseCase<Book>(postExecutionThread) {
override fun buildUseCaseCompletable(params: Book?): Completable {
return if (params != null && bookIsValid(params)) {
repository.saveBook(params)
} else {
Completable.error(IllegalArgumentException("Book is invalid"))
}
}
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
Presentation
Data Binding
• Facilita a ligação entre o
Presenter ou View Model e a
View
• Estende os arquivos de layout
com micro-expressões
• Muito útil em telas de input de
dados
<layout ...>
<data>
...
<import type="br.com.nglauber.livrosfirebase.model.MediaType" />
<variable name="book"
type="br.com.nglauber.livrosfirebase.model.Book" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<ImageView android:src="@{book.coverUrl}" ... />
<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}" ... />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
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
}
...
@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
}
ViewModel
Presentation
LiveData
• LiveData armazena dados observáveis
(Observable) e notifica os observadores
(Observer) quando esses dados mudam
para que a UI seja atualizada.
• O LiveData é lifecycle-aware. Ele só
notificará a UI se a Activity/Fragment
estiver no estado 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) {
if (id != state.value?.data?.id) {
state.postValue(ViewState(ViewState.Status.LOADING))
useCase.execute(id,
{ book ->
val bookBinding = BookConverter.fromData(book)
state.postValue(ViewState(ViewState.Status.SUCCESS, bookBinding))
},
{ e ->
state.postValue(ViewState(ViewState.Status.ERROR, error = e))
}
)
}
}
override fun onCleared() {
super.onCleared()
useCase.dispose()
}
}
class BookDetailsFragment : BaseFragment() {
...
private fun init() {
viewModel.getState().observe(this, Observer { viewState ->
when (viewState.status) {
ViewState.Status.SUCCESS -> binding.book = viewState.data
ViewState.Status.LOADING -> { /* show progress*/ }
ViewState.Status.ERROR -> { /* show error */}
}
})
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() {
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)))
useCase.execute(BookConverter.toData(book),
{
state.postValue(LiveEvent(ViewState(ViewState.Status.SUCCESS)))
},
{ e ->
state.postValue(LiveEvent(ViewState(ViewState.Status.ERROR, error = e)))
}
)
}
class BookFormFragment : BaseFragment() {
...
private fun init() {
viewModel.getState().observe(this, Observer { event ->
event?.consumeEvent()?.let { state ->
when (state.status) {
ViewState.Status.LOADING -> { ... }
ViewState.Status.SUCCESS -> { ... }
ViewState.Status.ERROR -> { ... }
}
}
})
}
Lifecycle
‣ Lifecycle é um objeto que define um ciclo de vida
‣ LifecycleOwner é uma interface para objetos com um
ciclo de vida
‣ Activity e Fragment implementam LifecycleOwner
e têm um Lifecycle.
‣ LifecycleObserver é uma interface para observar um
LifecycleOwner.
class BookListViewModel(
private val loadBooksUseCase: ListBooksUseCase,...
) : ViewModel(), LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun loadBooks() {
if (state.value == null) {
state.postValue(ViewState(ViewState.Status.LOADING))
loadBooksUseCase.execute(null,
{ books ->
val booksBinding = books.map { book ->
BookConverter.fromData(book)
}
state.postValue(
ViewState(ViewState.Status.SUCCESS, booksBinding)
)
},
{ e ->
state.postValue(
ViewState(ViewState.Status.ERROR, error = e)
)
}
)
}
}
class BookListFragment : BaseFragment() {
private val viewModel: BookListViewModel
private fun init() {
...
lifecycle.addObserver(viewModel)
}
...
UI (app)
Presentation
Domain
Remote Local
Data
UI (app)
Navigation API
• Serve para implementar o conceito de
“Single Activity” com múltiplos
Fragments/Views.
• Centraliza a lógica de navegação da
aplicação.
• Configuração direta com componentes de
UI (ActionBar, Menu, BottomNav, …)
• Simplifica a passagem de parâmetros.
<navigation ...
android:id="@+id/main_graph"
app:startDestination="@id/listBooks">
<fragment
android:id="@+id/listBooks"
android:name="dominando.android.livros.BookListFragment"
tools:layout="@layout/fragment_book_list"> ...
<action
android:id="@+id/action_list_to_details"
app:destination="@id/bookDetails" />
</fragment>
<fragment
android:id="@+id/bookDetails"
android:name="dominando.android.livros.BookDetailsFragment"
tools:layout="@layout/fragment_book_details"> ...
<argument
android:name="book"
app:argType="dominando.android.presentation.binding.Book"
app:nullable="true" />
</fragment> ...
</navigation>
<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()
}
}
// 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
https://android-developers.googleblog.com/2018/10/modern-background-execution-in-android.html
class MyWork(ctx: Context, params: WorkerParameters):
Worker(ctx, params) {
override fun doWork(): Result {
val firstName = inputData.getString(PARAM_FIRST_NAME)
outputData = Data.Builder()
.putString(PARAM_NAME, "$firstName Glauber")
.putInt(PARAM_AGE, 35)
.putLong(PARAM_TIME, System.currentTimeMillis())
.build()
return Result.SUCCESS
}
companion object {
const val PARAM_FIRST_NAME = "first_name"
const val PARAM_NAME = "name"
const val PARAM_AGE = "age"
const val PARAM_TIME = "time"
}
}
val input = Data.Builder()
.putString(MyWork.PARAM_FIRST_NAME, "Nelson")
.build()
// Uma vez
val request = OneTimeWorkRequest.Builder(MyWork::class.java)
.setInputData(input)
.build()
// ou Periódico
val request = PeriodicWorkRequest.Builder(
MyWork::class.java, 5, TimeUnit.MINUTES)
.setInputData(input)
.build()
observeAndEnqueue(request)
private val wm = WorkManager.getInstance()
private fun observeAndEnqueue(request: WorkRequest) {
wm.enqueue(request)
workId = request.id
wm.getWorkInfoByIdLiveData(request.id)
.observe(this, Observer { status ->
txtStatus.text = when (status?.state) {
WorkInfo.State.ENQUEUED -> "Enfileirado"
WorkInfo.State.BLOCKED -> "Bloqueado"
WorkInfo.State.CANCELLED -> "Cancelado"
WorkInfo.State.RUNNING -> "Executando"
WorkInfo.State.SUCCEEDED -> "Sucesso"
WorkInfo.State.FAILED -> "Falhou"
else -> "Indefinido"
}
txtOutput.text = status?.outputData?.run {
getString(MyWork.PARAM_NAME) +
getInt(MyWork.PARAM_AGE, 0) +
getLong(MyWork.PARAM_TIME, 0)
}
})
}
// Cancelando
workId?.let { uuid ->
wm.cancelWorkById(uuid)
}
wm.cancelAllWork()
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.setRequiresBatteryNotLow(false)
.setRequiresCharging(false)
.setRequiresStorageNotLow(false)
.build()
val request = OneTimeWorkRequest.Builder(MyWork::class.java)
.setConstraints(constraints)
.build()
val request0 = OneTimeWorkRequest.Builder(MyWork::class.java).build()
val request1 = OneTimeWorkRequest.Builder(Worker1::class.java).build()
val request2 = OneTimeWorkRequest.Builder(Worker2::class.java).build()
val request3 = OneTimeWorkRequest.Builder(Worker3::class.java).build()
wm.beginWith(request0)
.then(listOf(request1, request2))
.then(request3)
.enqueue()
Paging
class CharactersDataSourceFactory(
private val compositeDisposable: CompositeDisposable,
private val marvelApi: MarvelApi
) : DataSource.Factory<Int, Character>() {
override fun create(): DataSource<Int, Character> {
return CharactersDataSource(marvelApi, compositeDisposable)
}
}
class CharactersDataSource(
private val marvelApi: MarvelApi,
private val compositeDisposable: CompositeDisposable
) : PageKeyedDataSource<Int, Character>() {
// PositionalDataSource<Character>
// ItemKeyedDataSource<Int,Character>
override fun loadInitial(params: LoadInitialParams<Int>,
callback: LoadInitialCallback<Int, Character>) {
callback?.onResult(response.data.results, null, 1)
}
override fun loadAfter(params: LoadParams<Int>,
callback: LoadCallback<Int, Character>) {
callback?.onResult(response.data.results, params.key + 1)
}
override fun loadBefore(params: LoadParams<Int>,
callback: LoadCallback<Int, Character>) {
callback?.onResult(response.data.results, params.key - 1)
}
}
class CharactersViewModel : ViewModel() {
var characterList: Observable<PagedList<Character>>
private val compositeDisposable = CompositeDisposable()
private val sourceFactory: CharactersDataSourceFactory
init {
sourceFactory = CharactersDataSourceFactory(
compositeDisposable, MarvelApi.getService()
)
val config = PagedList.Config.Builder()
.setPageSize(20)
.setInitialLoadSizeHint(40)
.setPrefetchDistance(10)
.setEnablePlaceholders(false)
.build()
characterList = RxPagedListBuilder(sourceFactory, config)
.setFetchScheduler(Schedulers.io())
.buildObservable()
.cache()
}
...
}
class CharactersAdapter() : PagedListAdapter<Character, CharactersAdapter.VH>(CharacterDiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_character, parent, false)
return VH(view)
}
override fun onBindViewHolder(holder: VH, position: Int) {
val character = getItem(position)
holder.txtName.text = character?.name
holder.imgThumbnail.load(character?.thumbnail?.path)
}
class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imgThumbnail = itemView.imgThumbnail
val txtName = itemView.txtName
}
companion object {
val CharacterDiffCallback = object : DiffUtil.ItemCallback<Character>() {
override fun areItemsTheSame(oldItem: Character, newItem: Character): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Character, newItem: Character): Boolean {
return oldItem == newItem
}
}
}
}
#
#
🧐 #
#
#
#
🧐 #
🧐 #
• Você não precisa usar todos esses
recursos no mesmo app…
• Na verdade, você não precisa usar
nenhum!
• Mais importante do que saber quando
USAR é saber quando NÃO USAR 😉
• Mas é essencial conhecer tópicos,
saber seus prós e contras e utilizá-los
adequadamente 💡
github.com/googlesamples/android-architecture
Referências
• 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
@nglauber
+NelsonGlauber
www.nglauber.com.br

Obrigado!
youtube.com/nglauber

More Related Content

What's hot

20141001 delapsley-oc-openstack-final
20141001 delapsley-oc-openstack-final20141001 delapsley-oc-openstack-final
20141001 delapsley-oc-openstack-finalDavid Lapsley
 
The Many Ways to Build Modular JavaScript
The Many Ways to Build Modular JavaScriptThe Many Ways to Build Modular JavaScript
The Many Ways to Build Modular JavaScriptTim Perry
 
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
Nelson Glauber Leal
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
Robert DeLuca
 
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Domenic Denicola
 
Javascript Module Patterns
Javascript Module PatternsJavascript Module Patterns
Javascript Module Patterns
Nicholas Jansma
 
Building Large jQuery Applications
Building Large jQuery ApplicationsBuilding Large jQuery Applications
Building Large jQuery Applications
Rebecca Murphey
 
Android programming -_pushing_the_limits
Android programming -_pushing_the_limitsAndroid programming -_pushing_the_limits
Android programming -_pushing_the_limitsDroidcon Berlin
 
Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
Nelson Glauber Leal
 
Android Developer Toolbox 2017
Android Developer Toolbox 2017Android Developer Toolbox 2017
Android Developer Toolbox 2017
Shem Magnezi
 
A Rich Web experience with jQuery, Ajax and .NET
A Rich Web experience with jQuery, Ajax and .NETA Rich Web experience with jQuery, Ajax and .NET
A Rich Web experience with jQuery, Ajax and .NET
James Johnson
 
Simplified Android Development with Simple-Stack
Simplified Android Development with Simple-StackSimplified Android Development with Simple-Stack
Simplified Android Development with Simple-Stack
Gabor Varadi
 
HTML,CSS Next
HTML,CSS NextHTML,CSS Next
HTML,CSS Next
지수 윤
 
Organizing Code with JavascriptMVC
Organizing Code with JavascriptMVCOrganizing Code with JavascriptMVC
Organizing Code with JavascriptMVC
Thomas Reynolds
 
Mulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development ToolkitMulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development ToolkitRebecca Murphey
 

What's hot (20)

20141001 delapsley-oc-openstack-final
20141001 delapsley-oc-openstack-final20141001 delapsley-oc-openstack-final
20141001 delapsley-oc-openstack-final
 
droidparts
droidpartsdroidparts
droidparts
 
The Many Ways to Build Modular JavaScript
The Many Ways to Build Modular JavaScriptThe Many Ways to Build Modular JavaScript
The Many Ways to Build Modular JavaScript
 
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
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
 
Javascript Module Patterns
Javascript Module PatternsJavascript Module Patterns
Javascript Module Patterns
 
Building Large jQuery Applications
Building Large jQuery ApplicationsBuilding Large jQuery Applications
Building Large jQuery Applications
 
Android programming -_pushing_the_limits
Android programming -_pushing_the_limitsAndroid programming -_pushing_the_limits
Android programming -_pushing_the_limits
 
Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
 
JQuery
JQueryJQuery
JQuery
 
Android Developer Toolbox 2017
Android Developer Toolbox 2017Android Developer Toolbox 2017
Android Developer Toolbox 2017
 
A Rich Web experience with jQuery, Ajax and .NET
A Rich Web experience with jQuery, Ajax and .NETA Rich Web experience with jQuery, Ajax and .NET
A Rich Web experience with jQuery, Ajax and .NET
 
Simplified Android Development with Simple-Stack
Simplified Android Development with Simple-StackSimplified Android Development with Simple-Stack
Simplified Android Development with Simple-Stack
 
HTML,CSS Next
HTML,CSS NextHTML,CSS Next
HTML,CSS Next
 
Organizing Code with JavascriptMVC
Organizing Code with JavascriptMVCOrganizing Code with JavascriptMVC
Organizing Code with JavascriptMVC
 
Mulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development ToolkitMulberry: A Mobile App Development Toolkit
Mulberry: A Mobile App Development Toolkit
 
Drupal 8: Fields reborn
Drupal 8: Fields rebornDrupal 8: Fields reborn
Drupal 8: Fields reborn
 
BVJS
BVJSBVJS
BVJS
 
jQuery Presentasion
jQuery PresentasionjQuery Presentasion
jQuery Presentasion
 

Similar to Arquitetando seu aplicativo Android com Jetpack

Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
Nelson Glauber Leal
 
Getting Started with Combine And SwiftUI
Getting Started with Combine And SwiftUIGetting Started with Combine And SwiftUI
Getting Started with Combine And SwiftUI
Scott Gardner
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
James Casey
 
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
Yuriy Shapovalov
 
Knockoutjs UG meeting presentation
Knockoutjs UG meeting presentationKnockoutjs UG meeting presentation
Knockoutjs UG meeting presentation
Valdis Iljuconoks
 
SwiftUI and Combine All the Things
SwiftUI and Combine All the ThingsSwiftUI and Combine All the Things
SwiftUI and Combine All the Things
Scott Gardner
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
Andrew Dupont
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com Backbone
Rafael Felix da Silva
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
Knoldus Inc.
 
Single page webapps & javascript-testing
Single page webapps & javascript-testingSingle page webapps & javascript-testing
Single page webapps & javascript-testing
smontanari
 
JavaOne TS-5098 Groovy SwingBuilder
JavaOne TS-5098 Groovy SwingBuilderJavaOne TS-5098 Groovy SwingBuilder
JavaOne TS-5098 Groovy SwingBuilder
Andres Almiray
 
G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門
Tsuyoshi Yamamoto
 
Architecting Alive Apps
Architecting Alive AppsArchitecting Alive Apps
Architecting Alive Apps
Jorge Ortiz
 
Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLon
Fokke Zandbergen
 
Javascript first-class citizenery
Javascript first-class citizeneryJavascript first-class citizenery
Javascript first-class citizenery
toddbr
 
Intro to IndexedDB (Beta)
Intro to IndexedDB (Beta)Intro to IndexedDB (Beta)
Intro to IndexedDB (Beta)
Mike West
 
React on Rails - RailsConf 2017 (Phoenix)
 React on Rails - RailsConf 2017 (Phoenix) React on Rails - RailsConf 2017 (Phoenix)
React on Rails - RailsConf 2017 (Phoenix)
Jo Cranford
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
Nick Lee
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For Managers
AgileThought
 
Effective Android Data Binding
Effective Android Data BindingEffective Android Data Binding
Effective Android Data Binding
Eric Maxwell
 

Similar to Arquitetando seu aplicativo Android com Jetpack (20)

Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
 
Getting Started with Combine And SwiftUI
Getting Started with Combine And SwiftUIGetting Started with Combine And SwiftUI
Getting Started with Combine And SwiftUI
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
 
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
 
Knockoutjs UG meeting presentation
Knockoutjs UG meeting presentationKnockoutjs UG meeting presentation
Knockoutjs UG meeting presentation
 
SwiftUI and Combine All the Things
SwiftUI and Combine All the ThingsSwiftUI and Combine All the Things
SwiftUI and Combine All the Things
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com Backbone
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
 
Single page webapps & javascript-testing
Single page webapps & javascript-testingSingle page webapps & javascript-testing
Single page webapps & javascript-testing
 
JavaOne TS-5098 Groovy SwingBuilder
JavaOne TS-5098 Groovy SwingBuilderJavaOne TS-5098 Groovy SwingBuilder
JavaOne TS-5098 Groovy SwingBuilder
 
G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門G*ワークショップ in 仙台 Grails(とことん)入門
G*ワークショップ in 仙台 Grails(とことん)入門
 
Architecting Alive Apps
Architecting Alive AppsArchitecting Alive Apps
Architecting Alive Apps
 
Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLon
 
Javascript first-class citizenery
Javascript first-class citizeneryJavascript first-class citizenery
Javascript first-class citizenery
 
Intro to IndexedDB (Beta)
Intro to IndexedDB (Beta)Intro to IndexedDB (Beta)
Intro to IndexedDB (Beta)
 
React on Rails - RailsConf 2017 (Phoenix)
 React on Rails - RailsConf 2017 (Phoenix) React on Rails - RailsConf 2017 (Phoenix)
React on Rails - RailsConf 2017 (Phoenix)
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For Managers
 
Effective Android Data Binding
Effective Android Data BindingEffective Android Data Binding
Effective Android Data Binding
 

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 Multiplatform
Nelson 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 2023
Nelson 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 2023
Nelson 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 2021
Nelson 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 Android
Nelson 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 Android
Nelson 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 & Jetpack
Nelson 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 & Jetpack
Nelson 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 Android
Nelson Glauber Leal
 
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
Nelson Glauber Leal
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
Nelson 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 & Jetpack
Nelson Glauber Leal
 
Desenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos AndroidDesenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos Android
Nelson Glauber Leal
 
Desenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos AndroidDesenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos Android
Nelson Glauber Leal
 
Turbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com KotlinTurbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com Kotlin
Nelson 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 Layout
Nelson 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 Room
Nelson Glauber Leal
 
The world of Android Animations
The world of Android AnimationsThe world of Android Animations
The world of Android Animations
Nelson Glauber Leal
 
Android Constraint Layout
Android Constraint LayoutAndroid Constraint Layout
Android Constraint Layout
Nelson 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
 
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
 
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
 
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
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
 
Aplicações assíncronas no Android com Coroutines & Jetpack
Aplicações assíncronas no Android com Coroutines & JetpackAplicações assíncronas no Android com Coroutines & Jetpack
Aplicações assíncronas no Android com Coroutines & Jetpack
 
Desenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos AndroidDesenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos Android
 
Desenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos AndroidDesenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos Android
 
Turbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com KotlinTurbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com Kotlin
 
Tudo que você precisa saber sobre Constraint Layout
Tudo que você precisa saber sobre Constraint LayoutTudo que você precisa saber sobre Constraint Layout
Tudo que você precisa saber sobre Constraint Layout
 
Persistência de Dados no SQLite com Room
Persistência de Dados no SQLite com RoomPersistência de Dados no SQLite com Room
Persistência de Dados no SQLite com Room
 
The world of Android Animations
The world of Android AnimationsThe world of Android Animations
The world of Android Animations
 
Android Constraint Layout
Android Constraint LayoutAndroid Constraint Layout
Android Constraint Layout
 

Arquitetando seu aplicativo Android com Jetpack

  • 1. Arquitetando seu aplicativo Android com o Jetpack +Nelson Glauber @nglauber
 www.nglauber.com.br
 youtube.com/nglauber
  • 2. Porque pensar na arquitetura? ‣ Frameworks raramente forçam o desenvolvedor a seguir princípios. ‣ É fácil um desenvolvedor iniciante sem o devido acompanhamento criar um app ruim. ‣ Tecnologia e frameworks ocultam o propósito do aplicativo. ‣ “Sua arquitetura deve gritar o propósito do aplicativo”. A lógica de negócio deve ser claramente separada e independente de framework.
  • 3. Arquitetura ‣ Regra: Não há regras. Mas existem princípios que devem ser respeitados. Lembra do S.O.L.I.D.? ‣ Promove a organização e o desacoplamento do código. ‣ Devem facilitar a manutenção e adição de novas funcionalidades. ‣ Devem ser testáveis. ‣ Deixam o código mais complexo? Sim! Mas vale à pena. 😎
  • 4. MVP View IView Presenter Model Interação 
 do usuário Request DadosLógica de UI implements IPresenter implements
  • 5.
  • 6. MVVM View ViewModel Model Observes Interação 
 do usuário Dados Request
  • 7.
  • 8.
  • 11.
  • 12.
  • 13.
  • 16. ‣ Módulo Kotlin ‣ Normalmente classes puras ou interfaces ‣ Ser ou não ser reativo aqui… 🤔 Data
  • 18. import dominando.android.data.model.Book import io.reactivex.Completable import io.reactivex.Flowable interface BooksRepository { fun saveBook(book: Book): Completable fun loadBooks(): Flowable<List<Book>> fun loadBook(bookId: String): Flowable<Book> fun remove(book: Book): Completable }
  • 20. Local
  • 21. Room ‣ ORM para Android sobre o SQLite. ‣ Suporta atualização automática da UI com LiveData ou RXJava Local
  • 22.
  • 23. @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 )
  • 24. @Dao interface BookDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun save(book: Book): Completable @Delete fun delete(vararg book: Book): Completable @Query("SELECT * FROM Book WHERE title LIKE :title ORDER BY title") fun bookByTitle(title: String = "%"): Flowable<List<Book>> @Query("SELECT * FROM Book WHERE id = :id") fun bookById(id: String): Flowable<Book> }
  • 25. @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 } } }
  • 26. class RoomRepository(db: AppDatabase, private val fileHelper: FileHelper) : BooksRepository { private val bookDao = db.bookDao() override fun saveBook(book: Book): Completable { if (book.id.isBlank()) { book.id = UUID.randomUUID().toString() } return bookDao.save(BookConverter.fromData(book)) } override fun loadBooks(): Flowable<List<Book>> { return bookDao.bookByTitle() .map { books -> books.map { book -> BookConverter.toData(book) } } } ...
  • 28. ‣ Módulo Kotlin ‣ Abstração da lógica de negócio ‣ A lógica deve ser implementada aqui de forma abstrata. Domain
  • 29. open class ListBooksUseCase( private val repository: BooksRepository, postExecutionThread: PostExecutionThread ) : FlowableUseCase<List<Book>, Unit>(postExecutionThread) { override fun buildUseCaseFlowable(params: Unit?) : Flowable<List<Book>> { return repository.loadBooks() } }
  • 30. open class SaveBookUseCase( private val repository: BooksRepository, postExecutionThread: PostExecutionThread ): CompletableUseCase<Book>(postExecutionThread) { override fun buildUseCaseCompletable(params: Book?): Completable { return if (params != null && bookIsValid(params)) { repository.saveBook(params) } else { Completable.error(IllegalArgumentException("Book is invalid")) } } 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) ) } }
  • 32. Presentation Data Binding • Facilita a ligação entre o Presenter ou View Model e a View • Estende os arquivos de layout com micro-expressões • Muito útil em telas de input de dados
  • 33.
  • 34. <layout ...> <data> ... <import type="br.com.nglauber.livrosfirebase.model.MediaType" /> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <androidx.constraintlayout.widget.ConstraintLayout ...> <ImageView android:src="@{book.coverUrl}" ... /> <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}" ... /> ... </androidx.constraintlayout.widget.ConstraintLayout> </layout>
  • 35. 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 } ...
  • 36. @Parcelize class Book : BaseObservable(), Parcelable { @Bindable var id: String = "" set(value) { field = value notifyPropertyChanged(BR.id) } @Bindable var title: String = "" set(value) { field = value notifyPropertyChanged(BR.title) } // demais atributos }
  • 38. LiveData • LiveData armazena dados observáveis (Observable) e notifica os observadores (Observer) quando esses dados mudam para que a UI seja atualizada. • O LiveData é lifecycle-aware. Ele só notificará a UI se a Activity/Fragment estiver no estado STARTED ou RESUMED. Presentation
  • 39. class BookDetailsViewModel(private val useCase: ViewBookDetailsUseCase) : ViewModel() { private val state: MutableLiveData<ViewState<BookBinding>> = MutableLiveData() fun getState(): LiveData<ViewState<BookBinding>> = state fun loadBook(id: String) { if (id != state.value?.data?.id) { state.postValue(ViewState(ViewState.Status.LOADING)) useCase.execute(id, { book -> val bookBinding = BookConverter.fromData(book) state.postValue(ViewState(ViewState.Status.SUCCESS, bookBinding)) }, { e -> state.postValue(ViewState(ViewState.Status.ERROR, error = e)) } ) } } override fun onCleared() { super.onCleared() useCase.dispose() } }
  • 40. class BookDetailsFragment : BaseFragment() { ... private fun init() { viewModel.getState().observe(this, Observer { viewState -> when (viewState.status) { ViewState.Status.SUCCESS -> binding.book = viewState.data ViewState.Status.LOADING -> { /* show progress*/ } ViewState.Status.ERROR -> { /* show error */} } }) val book = arguments?.getParcelable<Book>("book") book?.let { viewModel.loadBook(book.id) } } }
  • 41. 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 }
  • 42. class BookFormViewModel(private val useCase: SaveBookUseCase) : ViewModel() { 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))) useCase.execute(BookConverter.toData(book), { state.postValue(LiveEvent(ViewState(ViewState.Status.SUCCESS))) }, { e -> state.postValue(LiveEvent(ViewState(ViewState.Status.ERROR, error = e))) } ) }
  • 43. class BookFormFragment : BaseFragment() { ... private fun init() { viewModel.getState().observe(this, Observer { event -> event?.consumeEvent()?.let { state -> when (state.status) { ViewState.Status.LOADING -> { ... } ViewState.Status.SUCCESS -> { ... } ViewState.Status.ERROR -> { ... } } } }) }
  • 44. Lifecycle ‣ Lifecycle é um objeto que define um ciclo de vida ‣ LifecycleOwner é uma interface para objetos com um ciclo de vida ‣ Activity e Fragment implementam LifecycleOwner e têm um Lifecycle. ‣ LifecycleObserver é uma interface para observar um LifecycleOwner.
  • 45. class BookListViewModel( private val loadBooksUseCase: ListBooksUseCase,... ) : ViewModel(), LifecycleObserver { ... @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun loadBooks() { if (state.value == null) { state.postValue(ViewState(ViewState.Status.LOADING)) loadBooksUseCase.execute(null, { books -> val booksBinding = books.map { book -> BookConverter.fromData(book) } state.postValue( ViewState(ViewState.Status.SUCCESS, booksBinding) ) }, { e -> state.postValue( ViewState(ViewState.Status.ERROR, error = e) ) } ) } }
  • 46. class BookListFragment : BaseFragment() { private val viewModel: BookListViewModel private fun init() { ... lifecycle.addObserver(viewModel) } ...
  • 48. UI (app) Navigation API • Serve para implementar o conceito de “Single Activity” com múltiplos Fragments/Views. • Centraliza a lógica de navegação da aplicação. • Configuração direta com componentes de UI (ActionBar, Menu, BottomNav, …) • Simplifica a passagem de parâmetros.
  • 49.
  • 50. <navigation ... android:id="@+id/main_graph" app:startDestination="@id/listBooks"> <fragment android:id="@+id/listBooks" android:name="dominando.android.livros.BookListFragment" tools:layout="@layout/fragment_book_list"> ... <action android:id="@+id/action_list_to_details" app:destination="@id/bookDetails" /> </fragment> <fragment android:id="@+id/bookDetails" android:name="dominando.android.livros.BookDetailsFragment" tools:layout="@layout/fragment_book_details"> ... <argument android:name="book" app:argType="dominando.android.presentation.binding.Book" app:nullable="true" /> </fragment> ... </navigation>
  • 52. 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() } }
  • 53. // 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
  • 56. class MyWork(ctx: Context, params: WorkerParameters): Worker(ctx, params) { override fun doWork(): Result { val firstName = inputData.getString(PARAM_FIRST_NAME) outputData = Data.Builder() .putString(PARAM_NAME, "$firstName Glauber") .putInt(PARAM_AGE, 35) .putLong(PARAM_TIME, System.currentTimeMillis()) .build() return Result.SUCCESS } companion object { const val PARAM_FIRST_NAME = "first_name" const val PARAM_NAME = "name" const val PARAM_AGE = "age" const val PARAM_TIME = "time" } }
  • 57. val input = Data.Builder() .putString(MyWork.PARAM_FIRST_NAME, "Nelson") .build() // Uma vez val request = OneTimeWorkRequest.Builder(MyWork::class.java) .setInputData(input) .build() // ou Periódico val request = PeriodicWorkRequest.Builder( MyWork::class.java, 5, TimeUnit.MINUTES) .setInputData(input) .build() observeAndEnqueue(request)
  • 58. private val wm = WorkManager.getInstance() private fun observeAndEnqueue(request: WorkRequest) { wm.enqueue(request) workId = request.id wm.getWorkInfoByIdLiveData(request.id) .observe(this, Observer { status -> txtStatus.text = when (status?.state) { WorkInfo.State.ENQUEUED -> "Enfileirado" WorkInfo.State.BLOCKED -> "Bloqueado" WorkInfo.State.CANCELLED -> "Cancelado" WorkInfo.State.RUNNING -> "Executando" WorkInfo.State.SUCCEEDED -> "Sucesso" WorkInfo.State.FAILED -> "Falhou" else -> "Indefinido" } txtOutput.text = status?.outputData?.run { getString(MyWork.PARAM_NAME) + getInt(MyWork.PARAM_AGE, 0) + getLong(MyWork.PARAM_TIME, 0) } }) }
  • 59. // Cancelando workId?.let { uuid -> wm.cancelWorkById(uuid) } wm.cancelAllWork()
  • 60. val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.NOT_REQUIRED) .setRequiresBatteryNotLow(false) .setRequiresCharging(false) .setRequiresStorageNotLow(false) .build() val request = OneTimeWorkRequest.Builder(MyWork::class.java) .setConstraints(constraints) .build()
  • 61. val request0 = OneTimeWorkRequest.Builder(MyWork::class.java).build() val request1 = OneTimeWorkRequest.Builder(Worker1::class.java).build() val request2 = OneTimeWorkRequest.Builder(Worker2::class.java).build() val request3 = OneTimeWorkRequest.Builder(Worker3::class.java).build() wm.beginWith(request0) .then(listOf(request1, request2)) .then(request3) .enqueue()
  • 63.
  • 64.
  • 65. class CharactersDataSourceFactory( private val compositeDisposable: CompositeDisposable, private val marvelApi: MarvelApi ) : DataSource.Factory<Int, Character>() { override fun create(): DataSource<Int, Character> { return CharactersDataSource(marvelApi, compositeDisposable) } }
  • 66. class CharactersDataSource( private val marvelApi: MarvelApi, private val compositeDisposable: CompositeDisposable ) : PageKeyedDataSource<Int, Character>() { // PositionalDataSource<Character> // ItemKeyedDataSource<Int,Character> override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Character>) { callback?.onResult(response.data.results, null, 1) } override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Character>) { callback?.onResult(response.data.results, params.key + 1) } override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Character>) { callback?.onResult(response.data.results, params.key - 1) } }
  • 67.
  • 68. class CharactersViewModel : ViewModel() { var characterList: Observable<PagedList<Character>> private val compositeDisposable = CompositeDisposable() private val sourceFactory: CharactersDataSourceFactory init { sourceFactory = CharactersDataSourceFactory( compositeDisposable, MarvelApi.getService() ) val config = PagedList.Config.Builder() .setPageSize(20) .setInitialLoadSizeHint(40) .setPrefetchDistance(10) .setEnablePlaceholders(false) .build() characterList = RxPagedListBuilder(sourceFactory, config) .setFetchScheduler(Schedulers.io()) .buildObservable() .cache() } ... }
  • 69.
  • 70. class CharactersAdapter() : PagedListAdapter<Character, CharactersAdapter.VH>(CharacterDiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_character, parent, false) return VH(view) } override fun onBindViewHolder(holder: VH, position: Int) { val character = getItem(position) holder.txtName.text = character?.name holder.imgThumbnail.load(character?.thumbnail?.path) } class VH(itemView: View) : RecyclerView.ViewHolder(itemView) { val imgThumbnail = itemView.imgThumbnail val txtName = itemView.txtName } companion object { val CharacterDiffCallback = object : DiffUtil.ItemCallback<Character>() { override fun areItemsTheSame(oldItem: Character, newItem: Character): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Character, newItem: Character): Boolean { return oldItem == newItem } } } }
  • 72. • Você não precisa usar todos esses recursos no mesmo app… • Na verdade, você não precisa usar nenhum! • Mais importante do que saber quando USAR é saber quando NÃO USAR 😉 • Mas é essencial conhecer tópicos, saber seus prós e contras e utilizá-los adequadamente 💡
  • 74. Referências • 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