quarta-feira, 20 de dezembro de 2017

Retrospectiva 2017

Olá povo,

Nos últimos anos tenho procurado escrever um post com a retrospectiva do ano que passou e esse ano não poderia ser diferente. O ano de 2017 foi bem mais calmo do que os dois ou três anteriores.
Em termos profissionais, nada mudou muito. Continuo na Mokriya, muito satisfeito com o trabalho home-office. Tive apenas mudanças de projeto e de cliente, mas nada muito drástico. Inclusive eu escrevi esse post aqui falando um pouquinho do trabalho remoto.
O que acelerou o ano foi a quantidade de palestras/eventos/viagens que fiz e que vou resumir rapidamente aqui pra vocês.

Aulas

Em 2016 parei de dar aulas na graduação e agora estou apenas ministrando aulas (quando convidado) em disciplinas de pós-graduação ou in-company. Esse ano só ministrei uma turma de pós graduação na FACISA em Campina Grande - PB. Turma muito boa e interessada.

Turma de pós-gradução em Mobile da FACISA

Palestras

Acho que esse foi o ano em que mais fiz palestras: 15 no total. Abaixo, listei os eventos com algumas fotos ;)

Android Meetup #6 GDG Recife (13/02)
Local: ThoughtWorks - Recife - PE
Palestra: Desenvolvimento moderno de aplicações Android

Hangout GDG Rio de Janeiro (25/05)
Local: On-line
Palestra: Constraint Layout

Aniversário de 4 anos do Meetup de Android do GDG São Paulo (10/06)
Local: Oxigênio Aceleradora - São Paulo - SP
Palestra: The world of Android Animations
Android Meetup 4 anos GDG São Paulo

The Developer's Conference (TDC) São Paulo (19 e 20/06)
Local: Universidade Anhembi Morumbi - São Paulo - SP
Palestra: Persistência de Dados no SQLite com Room
TDC São Paulo

GDG Recife Meetup I/O Recap (10/07)
Local: Centro de Informática da UFPE - Recife - PE
Palestra: Introdução ao Kotlin para Android

GDG Recife Tech Tour (22/07)
Local: Armazém da Criatividade - Caruaru - PE
Palestra #1: Turbinando o desenvolvimento Android com Kotlin
Palestra #2: Persistência de Dados no SQLite com Room
GDG Recife Tech Tour - Caruaru

Google Launchpad Build (05/08)
Local: TECNOPUC - Porto Alegre - RS
Palestra: Turbinando o desenvolvimento Android com Kotlin
Google Launchpad Build - PoA

Androidos (11 e 12/08)
Local: Unipê - João Pessoa - PB
Palestra: Turbinando o desenvolvimento Android com Kotlin
Androidos - João Pessoa

Google Agency Program (16/08)
Local: Google - São Paulo - SP
Palestra: Turbinando o desenvolvimento Android com Kotlin
Google São Paulo

Android Dev Conference (26/08)
Local: Hotel Pullman - São Paulo - SP
Palestra: Tudo que você precisa saber sobre Constraint Layout
Android Dev Conference

DevFest Maceió (23/09)
Local: Hotel Ritz Lagoa da Anta - Maceió - AL
Palestra: Turbinando o desenvolvimento Android com Kotlin
DevFest Maceió

DevFest Cerrado (30/09)
Local: Espaço Equatore - Goiânia - GO
Palestra: Turbinando o desenvolvimento Android com Kotlin
DevFest Cerrado

DevFest Paraná (11/11)
Local: SEBRAE - Maringá - PR
Palestra: Turbinando o desenvolvimento Android com Kotlin
DevFest Paraná

DevFest XP (09/12)
Local: InLoco - Recife - PE
Palestra: Desenvolvimento moderno de aplicações Android
DevFest XP em Recife

Os slides dessas palestras estão no meu slideshare ;)

Eventos

Participei de dois eventos sensacionais na Cracóvia (Polônia): o GDE Summit, que é o encontro anual dos GDEs (Google Developer Experts); e o Google Developer Days (GDD) Europe.
GDE Summit

GDD Europe

Aprendi muito nesses eventos e conheci e reencontrei muita gente bacana de todo o mundo por lá. Realmente foi uma experiência sensacional.

Passeios

Consegui duas mini-férias esse ano: uma em maio e outra dezembro, então deu para passear um pouquinho com minha esposa <3 p="">
Universal Studios - Orlando - EUA

Gramado - RS 

Consegui realizar um sonho pessoal de conhecer Londres. Infelizmente só pude passar dois dias, mas foi fantástico e muito marcante. Espero voltar lá com mais tempo algum dia.
Tower Bridge - Londres - Inglaterra

Blog, Vídeos, Livro, ... 

Infelizmente o blog ficou um pouco parado em 2017: foram apenas 5 posts. Entretanto, resolvi focar um pouco mais na criação de vídeos informais sobre coisas que estou estudando. Publiquei apenas 8 vídeos, mas que tiveram uma quantidade de visualizações muito maior do que os blogposts. Então provavelmente vou continuar trabalhando em mais vídeos para vocês ;)
Em relação ao livro, eu realmente pretendia lançar uma nova edição do "Dominando o Android" no começo de 2018, mas não deu :( demandas altas de trabalho, o grande número de viagens e palestras me fizeram adiar a nova edição do livro para 2018. Mas fiquem ligados, que eu sempre postarei as novidades por aqui ou em minhas redes sociais.

--

That's all folks! Essa foi a retrospectiva de 2017! Foi mais um ótimo ano para mim, e espero que tenha sido para vocês também! Que 2018 seja ainda melhor para todos nós.

4br4ç05,
nglauber

segunda-feira, 14 de agosto de 2017

Como eu aprendi Kotlin


Olá povo,

Tenho ministrado palestras sobre Kotlin em vários lugares e uma pergunta muito comum que o pessoal vem fazendo é: onde e como aprender Kotlin? 
Vou responder essa pergunta descrevendo como eu aprendi o que eu sei até agora.
Meu primeiro contato com Kotlin foi no segundo semestre de 2016 com o livro do Antonio Leiva que me deu uma boa base da linguagem e me motivou a estudar mais sobre o assunto.
Uma vez familiarizado com a linguagem, comecei a ler a documentação do Kotlin que é muito boa.
E navegando pela documentação achei o Kotlin Koans onde você aprende diversos recursos da linguagem por meio de 42 desafios que podem ser feitos on-line ou por meio do Kotlin Educational Plugin.

Após essas etapas iniciais, li um monte de artigos (muitos bem similares) onde cada um foi agregando um pouco aos meus estudos. Então resolvi compartilhar com vocês os links. Qualquer dúvida, é só falar ;)

Enjoy! :)

POSTS/SLIDES

Ten Kotlin Features To Boost Android Development

Kotlin: The Good, The Bad, and The Ugly

Why You Must Try Kotlin For Android Development ?

5 small things you probably don’t know about Kotlin

How “Effective Java” may have influenced the design of Kotlin

Kotlin 1.1: o que vem por aí?

Why You Should Start Using Kotlin to Supercharge Your Android Development in 2017

Android Coroutines with Kotlin: getting rid of runOnUiThread and Callbacks; cleaner thread handling; and more

Por que o Kotlin foi criado?

Lessons learned while converting to Kotlin with Android Studio

Exploring the Kotlin standard library

Handsome codes with Kotlin

Kotlin — A deeper look

Palestras do Jake Wharton
Nov/2015
Dez/2015
Mar/2017
Jul/2017

Kotlin Bytecode Generation and Runtime Performance

Understanding Generics and Variance in Kotlin

inline, noinline, crossinline — What do they mean?

An in-depth look at Kotlin’s initializers

Compare Java to Kotlin


Kotlin Workshop Material for you to use

Anko Coroutines

VÍDEOS

Kotlin para Android: O Despertar Da Força

Live code with Kotlin: construindo o primeiro app usando Kotlin

Kotlin in Real Projects

Learn Path: Kotlin in depth

Muito provavelmente, vocês encontrarão todos esses links no site https://kotlin.link/.

Ah! Mas obviamente todos esses links não valeriam de muita coisa sem praticar. Por isso, desde o ano passado, todos os meus projetos (pessoais e profissionais) foram (e estão sendo) em Kotlin! \o/

Qualquer dúvida, deixe seu comentário ;)

4br4ç05,
nglauber

quarta-feira, 5 de julho de 2017

Falando de Android por aí :)

Olá pessoal,

Passei aqui apenas para deixar a lista dos próximos eventos que eu participarei. Estão todos convidados! :)


✔︎ Meetup: I/O Recap do GDG Recife
10/07/2017 - 19:15
https://www.meetup.com/GDG-Recife/events/241094820
Palestra: Turbinando o Desenvolvimento Android com Kotlin


✔︎ The Developers Conference (TDC) São Paulo - Trilha Android
19/07/2017 - 15:40
http://www.thedevelopersconference.com.br/tdc/2017/saopaulo/trilha-android
Palestra: Persistência de dados no SQLite com Room



✔︎ GDG Tech Tour Caruaru: I/O Recap
22/07/2017 - 13:00
https://www.facebook.com/events/1266327943476326
Palestra: Turbinando o Desenvolvimento Android com Kotlin



✔︎ Google Launchpad Build Porto Alegre
05/08/2017
https://events.withgoogle.com/google-launchpad-build-porto-alegre-05-de-agosto/
Palestra: Turbinando o Desenvolvimento Android com Kotlin



✔︎ Androidos Day em João Pessoa
11/08/2017 - 18:00 (Mini-Curso)
12/08/2017 - 13:30
http://androidosday.com/
Palestra: Turbinando o Desenvolvimento Android com Kotlin


✔︎ Google Developer Agency Day em São Paulo
16/08/2017
https://events.withgoogle.com/google-developers-agency-day/
Palestra: Turbinando o Desenvolvimento Android com Kotlin


✔︎ Android Dev Conference
24 e 25/08/2017
https://eventos.imasters.com.br/android-devconference
Palestra: Tudo o que você precisa saber sobre ConstraintLayout



✔︎ DevFest Maceió
23/09/2017
https://devfest.gdgmaceio.org/
Palestra: Turbinando o Desenvolvimento Android com Kotlin


✔︎ DevFest Cerrado em Goiânia
30/09/2017
https://devfestcerrado.com.br/
Palestra: Turbinando o Desenvolvimento Android com Kotlin


DevFest Paraná (Londrina)
11/11/2017
https://devfestpr.org/
Palestra: ?

4br4ç05,
nglauber

segunda-feira, 3 de julho de 2017

Introdução ao Kotlin com Android Studio

Olá pessoal,

Decidi fazer esse vídeo para ajudar aqueles que querem dar os primeiros passos com Kotlin utilizando Android Studio.
Qualquer dúvida, deixe seu comentário. ;)

4br4ço5,
nglauber

sábado, 24 de junho de 2017

Um ano de trabalho em casa

Olá povo,

Após quase um ano trabalhando home-office full-time, resolvi compartilhar com vocês um pouco de como está sendo essa nova experiência na minha carreira.

Em maio de 2016, recebi o convite do meu amigo Douglas Drummond para me candidatar a uma vaga na empresa que ele estava trabalhando. A Mokriya (www.mokriya.com) é uma empresa indiana, mas com sede na cidade de Cupertino, Califórnia. Foi fundada em 2010 e adquirida pela Nagarro no final de 2016. Entre os principais clientes estão: Google, Twitter, Intel, AT&T, Verizon, Sony, SanDisk, Salesforce, Estee Lauder, entre outros.

Eu tinha acabado de sair do CESAR e entrado na MESA, mas a proposta de trabalhar de casa foi muito atraente, então resolvi me candidatar. Após três etapas de entrevistas, fui aprovado e comecei minha nova jornada.

Como essa é a minha primeira experiência em home-office, não sei como funciona em outras empresas, mas na Mokriya temos confiança em cada membro da equipe. Digo isso porque algumas pessoas já me perguntaram se eu tinha que ficar com a webcam ligada, ou reportar meu trabalho todo dia, etc. Eu reporto minhas atividades apenas durante as reuniões diárias (ou semanais, dependendo do projeto). Como normalmente a equipe é pequena, não dá para "enrolar", você tem que ser comprometido, focado e eficiente. Acho que isso serve para todos os trabalhos, mas na minha experiência atual isso ficou mais evidente.

Uma coisa importantíssima de trabalhar remoto é ter foco e disciplina, pois em casa é muito mais fácil se distrair, seja com projetos pessoais ou com qualquer bobeira na internet. O que eu procuro fazer é sempre estabelecer um horário de trabalho e as metas para o meu dia, então eu só paro quando esses objetivos são alcançados.

Existem vários benefícios em trabalhar remoto. O primeiro deles é que eu posso trabalhar onde e como quiser. Pode ser em casa, de bermuda e chinelo, escutando música alta e gritando que nem um louco (é... eu faço isso). Ou posso viajar para um evento ou para o interior ver meus pais. Ou posso ainda ir para um co-working ou para a piscina do prédio (pq eu nunca fiz isso???).

Outro grande benefício é não pegar o trânsito infernal de Recife que é um dos piores do Brasil. Isso me fez ganhar pelo menos 2 horas por dia para aproveitar como eu quiser :)

Mas esses benefícios não seriam nada, se o trabalho em si não fosse motivador. Desde que entrei na Mokriya, sempre trabalhei em projetos interessantes (uns mais que os outros, claro). E em cada um deles, uma equipe com pessoas de diversas partes do mundo: Bulgária, Polônia, Índia, Estados Unidos, Portugal, ... e com isso, consigo treinar meu inglês com os mais diferentes sotaques. :)

Além dos motivos que mencionei acima, trabalhar na Mokriya me permitiu realizar um sonho profissional que era: "trabalhar no Google". Nunca fui funcionário do Google, mas estava alocado em um projeto e passei algumas semanas trabalhando no Googleplex em Mountain View. Eu já tinha ido ao Google algumas vezes para eventos do programa de GDE (Google Developer Expert) e durante o Google I/O, mas trabalhar lá (participar de reuniões, conhecer o ambiente, fazer as refeições, andar nas bikes :) e ver tudo aquilo por dentro foi sensacional.. indescritível.

Mas nem tudo são flores. Trabalhar de casa tem seus pontos negativos, e para mim, a solidão é o pior de todos. Por onde trabalhei, sempre gostava de conversar com todo mundo, tomar aquele café com a equipe nos intervalos, falar como foi o fim de semana, etc. Em casa, você até tem o café, mas não rola aquela conversa bacana. Entretanto eu tento minimizar esse problema almoçando semanalmente com os meus velhos amigos do CESAR. ;)

Às vezes, os fuso-horários diferentes para reuniões são bem inconvenientes. Para mim não foi tão complicado, o pior que me aconteceu foram reuniões às 20h ou no horário do almoço/janta, já para alguns colegas, as reuniões era à 1h ou 2h da manhã... #tenso
Trabalhar nos feriados do Brasil é bem chato, mas venhamos e convenhamos, temos feriados demais por aqui e desde que entrei na Mokriya, eu trabalhei em TODOS! :'(
Meu contrato é de consultoria, então não tenho os "benefícios" da CLT como: 13º, FGTS e INSS (eu pago por fora) e além disso, tenho que pagar o plano de saúde por fora.

Depois desse tempo trabalhando remoto, cheguei a conclusão que, para empresas e profissionais de desenvolvimento de software, não faz nenhum sentido sair de casa e ir para um local específico trabalhar. Se você tiver uma boa estrutura, tudo pode ser feito de casa. Minha comunicação do dia-a-dia é feita por Slack, as reuniões são feitas com o Zoom, código no GitHub, ... isso sem contar Google Drive, Dropbox, Zeplin, HockeyApp, etc. para compartilhamento de arquivos.

É isso pessoal! Estou muito feliz nesse meu emprego e agradeço a Deus todos os dias por ele.
Qualquer dúvida, deixem seus comentários que eu vou atualizando o post ;)

4br4ç05,
nglauber

segunda-feira, 22 de maio de 2017

Architecture Components do Android

Olá povo,

O Google I/O 2017 foi repleto de novidades fantásticas para a plataforma Android. Entre elas, duas foram fundamentais para os desenvolvedores Android: Kotlin e Architecture Components.

Apesar de podermos desenvolver aplicativos Android utilizando Kotlin nas versões anteriores do Android Studio por meio de um plugin instalado à parte, agora ao criar um projeto poderemos selecionar a linguagem de nossa preferência (Java ou Kotlin).

Os Architecture Components vieram para facilitar a implementação de aplicativos de uma forma mais robusta e padronizada, e nesse post vou mostrar um exemplo simples de como utilizar essas APIs.


Adicionando as dependências

Para implementar esse projeto, eu utilizei o Android Studio 2.4 com o plugin 1.1.2 do Kotlin. Mas se quiser utilizar a versão preview do Android Studio 3.0 eu creio que você não terá muitos problemas. Crie um novo projeto com suporte a Kotlin e nomeie a sua activity principal como ListPeopleActivity. Com o projeto criado, vamos fazer as seguintes mudanças no build.gradle do seu projeto como a seguir:

buildscript {
    ext.gradle_version = '2.3.2'
    ext.kotlin_version = '1.1.2-3'
    ext.anko_version = '0.8.2'
    ext.support_version = '25.3.1'
    ext.arch_lifecycle_version = "1.0.0-alpha1"
    ext.arch_room_version = "1.0.0-alpha1"

    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:$gradle_version"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
allprojects {
    repositories {
        jcenter()
        maven { url 'https://maven.google.com' }
        mavenCentral()
    }
}
Aqui criamos uma série de variáveis com as versões das bibliotecas que utilizaremos no projeto. Isso não é obrigatório, mas facilita a legibilidade do arquivo e evita erros e conflitos entre versões das bibliotecas. Adicionamos a versão do Gradle, do Kotlin, do Anko (que é uma biblioteca para facilitar o acesso aos componentes de UI), da support library e finalmente, das bibliotecas de arquitetura (lifecycle e room).
Na lista de dependências, além do gradle, adicionamos o plugin do Kotlin. Por fim, na lista de repositórios, devemos adicionar o "maven.google.com".

Agora vá até o build.gradle do módulo da sua aplicação e faça os seguintes ajustes.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    // Nada muda aqui...
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jetbrains.anko:anko-common:$anko_version"

    compile "com.android.support:appcompat-v7:$support_version"
    compile "com.android.support:design:$support_version"
    compile "com.android.support:support-v4:$support_version"

    compile "android.arch.lifecycle:runtime:$arch_lifecycle_version"
    compile "android.arch.lifecycle:extensions:$arch_lifecycle_version"
    compile "android.arch.persistence.room:runtime:$arch_room_version"

    kapt "android.arch.lifecycle:compiler:$arch_lifecycle_version"
    kapt "android.arch.persistence.room:compiler:$arch_room_version"
}
Além do plugin do Android, estamos aplicando o plugin do Kotlin e o de extensões para o Kotlin. Na seção de dependências, adicionamos todas as bibliotecas que utilizaremos nesse projeto. O kapt (Kotlin Annotation Process Tool) substitui o "annotationProcessor" para projeto sem Kotlin. Perceba que utilizamos o kapt para as bibliotecas de arquitetura, pois ela geram o código em tempo de compilação. Isso deve ser feito para outras bibliotecas que fazem esse trabalho (Dagger, DataBinding,  etc.).

Definindo o acesso ao banco

Para simplificar o tutorial e facilitar o entendimento, nosso aplicativo será um cadastro simples de pessoas, onde cada pessoa possuirá o primeiro nome, sobrenome e idade. Para tal, definiremos a classe a seguir:

import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import java.io.Serializable

@Entity
data class Person(
        @PrimaryKey(autoGenerate = true) var id: Long = 0L,
        var firstName: String = "",
        var lastName: String = "",
        var age: Int = 0) : Serializable
Criamos uma data class anotada com @Entity, o que indica que objetos dessa classe serão persistidos. Todos os parâmetros têm valores default, então podemos criar objetos dessa classe passando nenhum parâmetro. Uma outra curiosidade nessa classe é que devemos adicionar um atributo como @PrimaryKey, nesse exemplo, ele será gerado automaticamente, então definimos a propriedade autoGenerate para true.

Agora vamos definiremos a DAO, a interface que possuirá os métodos para interagir com o banco de dados.
import android.arch.lifecycle.LiveData
import android.arch.persistence.room.*
import android.arch.persistence.room.OnConflictStrategy.IGNORE

@Dao
interface PeopleDao {

    @Insert(onConflict = IGNORE)
    fun insertPerson(person: Person)

    @Update
    fun updatePerson(person: Person)

    @Delete
    fun deletePerson(vararg people: Person)

    @Query("SELECT * FROM Person ORDER BY firstName")
    fun listAll(): LiveData<List<Person>>
}
Nossa interface está anotada com @Dao e os métodos de inserir, atualizar, excluir e listar estão anotados respectivamente com @Insert, @Update, @Delete e @Query. O método insertPerson(Person) possui em sua anotação a propriedade onConflict definida como IGNORE, indicando que caso haja um conflito no momento da inserção o registro será ignorado.
Apesar de não estarmos utilizando esse recurso, no método deletePerson() permitimos excluir vários registros ao mesmo tempo utilizando um vararg. A parte mais interessante é o método listAll() que retorna um LiveData de uma lista de objetos Person.
Utilizando o LiveData quando qualquer registro for inserido, alterado ou excluído, a lista será automaticamente atualizada.
O último passo para podermos utilizar o Room, é definir uma classe que herda de RoomDatabase como mostraremos a seguir.
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context

@Database(
        entities = arrayOf(Person::class),
        version = 1)
abstract class AppDatabase : RoomDatabase() {

    abstract fun peopleDao(): PeopleDao

    companion object {

        private val DB_NAME = "dbPeople"
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase? {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(
                            context.applicationContext,
                            AppDatabase::class.java,
                            DB_NAME)
                        .allowMainThreadQueries()
                        .build()
            }
            return INSTANCE
        }

        fun destroyInstance() {
            INSTANCE = null
        }
    }
}
Na anotação @Database informamos quais entidades serão persistidas utilizando a propriedade "entities" e a versão do banco. Perceba que essa é uma classe abstrata, pois assim como a nossa interface DAO, a implementação será gerada em tempo de compilação.
Essa classe será um singleton que é inicializado por meio do método Room.databaseBuilder onde passamos o contexto, a referência à nossa classe e o nome do banco. Perceba que estamos utilizando o método allowMainThreadQueries() que não é recomendado, pois devemos realizar as consultas em uma thread separada, mas não estamos fazendo isso aqui para simplificar o exemplo.
O método peopleDao() retornará uma instância da implementação do nosso DAO.

Definindo os ViewModels

Definida nossa camada de acesso ao banco, partiremos agora para a definição dos ViewModels do nosso pequeno projeto. Primeiramente, vamos criar uma classe que será utilizada na tela de listagem e será responsável por manter o resultado da busca durante a rotação da tela do aparelho sem ter que fazer uma nova consulta ao banco.
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.LiveData

class ListPeopleViewModel(app : Application) : AndroidViewModel(app) {

    var livePeople: LiveData<List<Person>>? = null
    var db: PeopleDao? = null

    private fun getDao() : PeopleDao? {
        if (db == null) {
            db = AppDatabase.getDatabase(getApplication())?.peopleDao()
        }
        return db
    }

    fun getPeople(): LiveData<List<Person>> {
        if (livePeople == null) {
            livePeople = getDao()?.listAll()
        }
        return livePeople as LiveData<List<Person>>
    }
}
Nossa classe herda de AndroidViewModel e está mantendo a LiveData da lista de Person. Dessa forma, caso a lista seja nula, carregamos do banco, caso contrário apenas a retornamos.
Faremos uma classe similar para a tela de cadastro, mas apenas para isolar o acesso ao banco da nossa UI.
import android.app.Application
import android.arch.lifecycle.AndroidViewModel

class PersonFormViewModel(app : Application) : AndroidViewModel(app) {

    private var db: PeopleDao? = null

    private fun peopleDao() : PeopleDao? {
        if (db == null) {
            db = AppDatabase.getDatabase(getApplication())?.peopleDao()
        }
        return db
    }

    fun savePerson(person : Person) {

        if (person.id == 0L) {
            peopleDao()?.insertPerson(person)

        } else {
            peopleDao()?.updatePerson(person)
        }
    }

    fun deletePerson(person: Person) {
        peopleDao()?.deletePerson(person)
    }
}
O método savePerson(Person) irá inserir o objeto se o seu id for igual a zero ou atualizá-lo caso contrário. Já o método deletePerson(Person) excluirá o objeto Person.

Juntando tudo

Finalmente vamos implementar as telas da aplicação, começando pela tela de listagem.
import android.arch.lifecycle.LifecycleRegistry
import android.arch.lifecycle.LifecycleRegistryOwner
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_list_people.*

class ListPeopleActivity : AppCompatActivity(), LifecycleRegistryOwner {

    var lifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): LifecycleRegistry = lifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_list_people)

        fab.setOnClickListener {
            PersonFormFragment().show(supportFragmentManager, "form")
        }

        val model = ViewModelProviders.of(this).get(ListPeopleViewModel::class.java)
        model.getPeople().observe(this, Observer { people ->
            listview.adapter = ArrayAdapter<Person>(
                    this@ListPeopleActivity,
                    android.R.layout.simple_list_item_1,
                    people)

            listview.setOnItemClickListener { _, _, position, _ ->
                val person = people?.get(position)
                if (person != null) {
                    PersonFormFragment.newInstance(person).show(supportFragmentManager, "form")
                }
            }
        })
    }

    override fun onDestroy() {
        AppDatabase.destroyInstance()
        super.onDestroy()
    }
}
Nossa classe herda de AppCompatActivity e implementa LifecycleRegistryOwner. Essa interface requer que implementemos o método getLifecycle() que retorna um objeto LifecycleRegistry. Poderíamos simplesmente herdade de LifecycleActivity, mas essa classe herda de FragmentActivity, então perderíamos a ActionBar padrão que é adicionada.

Não vou mostrar o layout dessa activity aqui, mas ele possui apenas uma ListView com o id @+id/listview e uma FloatingActionButton com o id @+id/fab. Perceba que estamos acessando os componentes de UI usando simplesmente o seu id. Isso é feito graças ao kotlinx (Kotlin Extensions).

Ao clicar no FAB, o PersonFormFragment (que mostraremos a seguir) é exibido. Perceba que estamos obtendo o ViewModel dessa tela por meio da classe ViewModelProviders e passando o nome da classe.
Com o ViewModel, obtemos a lista de pessoas e chamamos o método observe para sabermos quando a listagem mudou. Nesse caso, definimos um adapter para o nosso listView e atribuímos o evento de click no item. Perceba que como não estamos utilizando todos os parâmetros, estamos utilizando o "_" no lugar dos respectivos nomes.

A última classe é tela de cadastro que é um DialogFragment.
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_person_form.*

class PersonFormFragment : DialogFragment() {

    lateinit var person : Person
    lateinit var viewModel : PersonFormViewModel

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        viewModel = ViewModelProviders.of(this).get(PersonFormViewModel::class.java)
        person = arguments?.getSerializable(EXTRA_PERSON) as? Person ?: Person()
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater?.inflate(R.layout.fragment_person_form, container, false)
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        edtFirstName.setText(person.firstName)
        edtLastName.setText(person.lastName)
        edtAge.setText(if (person.age != 0 || person.id != 0L) person.age.toString() else "")

        btnRemove.visibility = if (person.id == 0L) View.GONE else View.VISIBLE

        btnCancel.setOnClickListener { dismiss() }
        btnSave.setOnClickListener { savePerson() }
        btnRemove.setOnClickListener { removePerson() }
    }

    private fun removePerson() {
        viewModel.deletePerson(person)
        dismiss()
    }

    private fun savePerson() {
        person.firstName = edtFirstName.text.toString()
        person.lastName = edtLastName.text.toString()
        person.age = edtAge.text.toString().toInt()

        viewModel.savePerson(person)
        dismiss()
    }

    companion object {
        val EXTRA_PERSON = "person"

        fun newInstance(person: Person) : PersonFormFragment {
            val args = Bundle()
            args.putSerializable(EXTRA_PERSON, person)

            val f = PersonFormFragment()
            f.arguments = args
            return f
        }
    }
}
Mais uma vez estamos omitindo o arquivo de layout aqui, mas ele possui basicamente três EditText (edtFirstName, edtLastName e edtAge) para os respectivos campos da classe Person, e três botões (btnSave, btnRemove, btnCancel) para salvar, remover e cancelar o dialog. Sendo que o segundo só estará habilitado se estivermos editando uma pessoa.
No onAttach estamos inicializando nosso ViewModel e obtendo o objeto Person passado da tela de listagem. Se não tivermos passado esse objeto, criamos um novo. Perceba que nessa instrução estamos usando "?" nos arguments, pois ele pode ser null, no "as?" pois não pode ser feito um cast de null para Person (isso se chama save cast) e por fim utilizamos o "elvis operator", pois caso a instrução anterior seja nula, criamos um novo objeto Person.
Nada de especial no onCreateView, mas no onViewCreated temos apenas uma instrução interessante para quem é novo em Kotlin que é o if/else retornando um valor (seria o mesmo que o ternário do Java "?:").
Nos métodos removePerson chamamos nosso ViewModel para excluir o objeto e no savePerson para salvar o objeto. Por fim, nossa classe possui o "factory method" newInstance para passarmos o objeto pessoa como parâmetro para a tela de cadastro no caso de uma alteração.

O resultado deve ficar como a seguir:


Essa foi uma pequena introdução de como utilizar as bibliotecas de arquitetura lançadas no Google IO de 2017 utilizando Kotlin. Obviamente existe muito mais a explorar, mas espero que esse post já ajude a dar os primeiros passos com essas APIs.

Para saber mais, assistam esses vídeos do Google IO e a documentação dessas libs. ;)

Architecture Components - Introduction
https://www.youtube.com/watch?v=FrteWKKVyzI

Architecture Components - Solving the Lifecycle Problem
https://www.youtube.com/watch?v=bEKNi1JOrNs

Architecture Components - Persistence and Offline
https://www.youtube.com/watch?v=MfHsPGQ6bgE

Documentação oficial
https://developer.android.com/topic/libraries/architecture/index.html

4br4ç05,
nglauber

sexta-feira, 3 de março de 2017

RXJava + Kotlin + Retrofit + Star Wars API



Olá povo,

Há algum tenho venho estudando dois tópicos relacionados a Android que vem me deixando bem empolgado: Kotlin e RX Java.
Kotlin é uma liguagem dinâmica para JVM desenvolvida pela JetBrains que traz diversas features não existentes no Java. E o RX Java, bem a grosso modo, é uma biblioteca nos ajuda a trabalhar com sequência de dados que podem estar em threads separadas de uma maneira bem mais simples.

O intuito deste post não é como dar os primeiros passos com RX ou Kotlin, e sim documentar e compartilhar o que eu aprendi ao tentar implementar um exemplo "simples" com esse conjunto de linguagem+biblioteca+api.

Nesse post vou mostrar:
- Como acessar a Star Wars API utilizando a biblioteca Retrofit;
- Fazer as requisições utilizando RX Java + Retrofit;
- e todo o código é escrito em Kotlin.

Configuração do projeto

Primeira coisa que você deve fazer é instalar o plugin do Kotlin no Android Studio. Você pode seguir esse tutorial aqui:
https://blog.jetbrains.com/kotlin/2013/08/working-with-kotlin-in-android-studio/
A versão atual no momento da escrita desse post é a 1.1.0-release-Studio2.3-1

Instalado o plugin, crie um novo projeto no Android Studio.
Deixe o build.gradle do seu projeto como a seguir:
buildscript {
    ext.kotlin_version = '1.1.0'
    ext.appcompat_version = '25.1.0'
    ext.retrofit_version = '2.2.0'

    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

O que temos de diferente aqui é que estamos criando algumas variáveis com as versões do Kotlin e da biblioteca de compatibilidade. E na seção de dependências adicionamos o plugin do Kotlin para o Gradle.
Vá agora até o build.gradle do módulo, faça as seguintes alterações:
apply plugin: 'com.android.application'
apply plugin: "kotlin-android"

android {
...
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    // Dependência da linguagem Kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    // AppCompat
    compile "com.android.support:appcompat-v7:$appcompat_version"
    // RXJava
    compile 'io.reactivex:rxjava:1.2.5'
    // RXAndroid para termos acesso a main thread do Android 
    compile 'io.reactivex:rxandroid:1.2.1'
    // Retrofit
    compile "com.squareup.retrofit2:retrofit:$retrofit_version"
    // Adapter do Retrofit para retornar objetos observáveis
    compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
    // Converter do Retrofit para utilizar o Gson para tratar a resposta do servidor
    compile "com.squareup.retrofit2:converter-gson:$retrofit_version"
    // Interceptor para visualizar os logs das requisições do Retrofit
    compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
    ...
}
Aplicamos o plugin do Kotlin e adicionamos as dependências que utilizaremos no projeto. A motivação de cada uma está comentada acima.

A API do Star Wars

Vamos utilizar nesse exemplo dois endpoints da API do Star Wars: films e people.
Se fizermos uma requisição para http://swapi.co/api/films o resultado será o JSON com a lista dos filmes de Star Wars.
{
    "count": 7, 
    "next": null, 
    "previous": null, 
    "results": [
        {
            "characters": [
                "http://swapi.co/api/people/1/",
                ...
            ],
            "created": "2014-12-10T14:23:31.880000Z",
            "director": "George Lucas",
            "edited": "2014-12-12T11:24:39.858000Z",
            "episode_id": 4,
            "opening_crawl": "It is a period of civil war..",
            "planets": [
                "http://swapi.co/api/planets/1/",
                ...
            ],
            "producer": "Gary Kurtz, Rick McCallum",
            "release_date": "1977-05-25",
            "species": [
                "http://swapi.co/api/species/1/",
                ...
            ],
            "starships": [
                "http://swapi.co/api/starships/2/",
                ...
            ],
            "title": "A New Hope",
            "url": "http://swapi.co/api/films/1/",
            "vehicles": [
                "http://swapi.co/api/vehicles/4/",
                ...
            ]
        },
        // Aqui viriam os demais filmes...
    ]
}
Se utilizarmos http://swapi.co/api/films/1 ele trará apenas o filme primeiro filme.
Percebam que os campos "characters", "planets", "species", "starships" e "vehicles" retornam um array de strings, onde cada string representa o endereço para aquela determinada informação. Sendo assim, se acessarmos a URL http://swapi.co/api/people/1/ teremos o resultado abaixo.
{
    "name": "Luke Skywalker", 
    "height": "172", 
    "mass": "77", 
    "hair_color": "blond", 
    "skin_color": "fair", 
    "eye_color": "blue", 
    "birth_year": "19BBY", 
    "gender": "male", 
    "homeworld": "http://swapi.co/api/planets/1/", 
    "films": [
        "http://swapi.co/api/films/6/", 
        "http://swapi.co/api/films/3/", 
        "http://swapi.co/api/films/2/", 
        "http://swapi.co/api/films/1/", 
        "http://swapi.co/api/films/7/"
    ], 
    "species": [
        "http://swapi.co/api/species/1/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/14/", 
        "http://swapi.co/api/vehicles/30/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/22/"
    ], 
    "created": "2014-12-09T13:50:51.644000Z", 
    "edited": "2014-12-20T21:17:56.891000Z", 
    "url": "http://swapi.co/api/people/1/"
}
Note que temos uma referência cruzada aqui. O filme possui a lista de personagens e o personagem possui uma lista dos filmes (no campo "films") em que ele participou.
Entendida a API, vamos começar a brincar com ela!

Definindo as classes de modelo

Um dos recursos que eu gosto bastante do Kotlin é a possibilidade de criar data classes, que são os nosso famosos POJOs. É possível criar várias classes públicas no mesmo arquivo e no Kotlin temos o conceito de propriedade, ou seja, não é preciso definir os gets e sets (embora você possa customiza-los).
Crie o arquivo DataClassesWeb.kt (ou o nome que preferir) que conterá as classes que representarão o retorno das requisições que faremos a API.
package br.com.nglauber.starwarsrx.model.api

import com.google.gson.annotations.SerializedName

data class FilmResult(val results : List<Film>)

data class Film (val title : String,
                 @SerializedName("episode_id")
                 val episodeId : Int,
                 @SerializedName("characters")
                 val personUrls : List<String>)

data class Person(val name : String,
                  val gender : String)

A classe FilmResult representará o retorno da chamada que faremos a lista de filmes. Ela possui a propriedade results que é uma lista de Film. A classe Film, por sua vez, possui o título, o id do episódio e a lista das URLs para obtermos as informações dos personagens. Por fim, a classe Person possui o nome e o gênero do personagem.

Agora crie mais um arquivo chamado DataClasses.kt com as classes "de negócio" da nossa aplicação.
package br.com.nglauber.starwarsrx.model

data class Movie (val title : String,
                  val episodeId : Int,
                  val characters : MutableList<Character>)

data class Character(val name : String,
                     val gender : String){

    override fun toString(): String {
        return "${name} / ${gender}"
    }
}
Como podemos observar, essas classes são bem parecidas, mas preferi separar as classes de retorno de API, das que serão utilizadas na UI.

Definindo as chamadas à API com Retrofit

Nesse exemplo, vamos utilizar apenas dois endpoints da API do Star Wars: o que retorna a listagem de filmes; e o que obtém o personagem pelo seu id. Sendo assim, crie o arquivo StarWarsApiDef.kt e deixe-o como a seguir:
package br.com.nglauber.starwarsrx.model.api

import retrofit2.http.GET
import retrofit2.http.Path
import rx.Observable

interface StarWarsApiDef {
  @GET("films")
  fun listMovies() : Observable<FilmResult>

  @GET("people/{personId}")
  fun loadPerson(@Path("personId") personId : String) : Observable<Person> 
}
Os métodos seguem o que está especificado na API do Star Wars. Para obtermos a lista de filmes, realizamos uma requisição do tipo GET, para o endpoint "films" que retorna um objeto (FilmResult) que possui uma lista de filmes (Film). E para obter um personagem específico, utilizamos o endpoint "people/id_do_personagem".
Percebam que estamos retornando um Observable<FilmResult> e um Observable<Person>. A classe Observable é um dos principais componentes do RXJava (senão o principal). Se você não está familiarizado com esses conceitos, sugiro assistir as palestras do Ubiratan Soares que é uma verdadeira aula sobre o assunto (veja os links no final do post).

Definida a classe com os endpoints, vamos criar a implementação que utilizará esses endpoints. Crie o arquivo StarWarsApi.kt e deixe-o como a seguir.
package br.com.nglauber.starwarsrx.model.api

import br.com.nglauber.starwarsrx.model.Character
import br.com.nglauber.starwarsrx.model.Movie
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import rx.Observable
import java.util.*

class StarWarsApi {
  val service: StarWarsApiDef

  init {
      val logging = HttpLoggingInterceptor()
      logging.level = HttpLoggingInterceptor.Level.BODY

      val httpClient = OkHttpClient.Builder()
      httpClient.addInterceptor(logging)

      val gson = GsonBuilder().setLenient().create()

      val retrofit = Retrofit.Builder()
            .baseUrl("http://swapi.co/api/")
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(httpClient.build())
            .build()

      service = retrofit.create<StarWarsApiDef>(StarWarsApiDef::class.java)
  }

  fun loadMovies(): Observable<Movie>? {
    return service.listMovies()
            .flatMap { filmResults -> Observable.from(filmResults.results) }
            .map { film ->
                Movie(film.title, film.episodeId, ArrayList<Character>())
            }
    }
}
Essa classe possui um atributo chamado service do tipo StarWarsApiDef (que criamos anteriormente). Dentro do bloco init{} fazemos a inicialização e configuração do serviço do Retrofit. Adicionamos o HttpLoggingInterceptor ao OkHttpClient para podermos visualizar no Logcat as requisições e as respostas feitas pelo retrofit. Instanciamos o GsonBuilder para que o JSON retornado seja tratado pela biblioteca Gson. Utilizamos o RxJavaCallAdapterFactory para o Retrofit retornar o resultado em forma de objetos observáveis. Por fim, utilizamos o Retrofit.Builder para criar a instância do serviço.
O operador flatMap permite iterar sobre um Observable e retornar um novo Observable. É isso que estamos fazendo no método loadMovies. Estamos chamando o método listMovies() do nosso serviço que retorna um Observable de FilmResult, então utilizamos o operador flatMap para obter o FilmResult e geramos um novo Observable de Film com os filmes por meio do método Observable.from(). Em seguida, iterarmos por cada filme (Film) da lista (que é um Observable de Film) e o transformamos em um Observable de Movie, que é o tipo de retorno do método.

Chamando o serviço na Activity

Vamos ver como acessar o nosso serviço na interface gráfica. Se você ainda não converteu sua activity para Kotlin, faça isso acessando o menu "Code > Convert Java File to Kotlin file". E deixe sua activity como a seguir.
class MainActivity : AppCompatActivity() {

    lateinit var listView : ListView
    lateinit var movieAdapter : ArrayAdapter<String>
    var movies = mutableListOf<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        listView = ListView(this)
        setContentView(listView)
        movieAdapter = ArrayAdapter(this, 
                android.R.layout.simple_list_item_1, movies)
        listView.adapter = movieAdapter

        val api = StarWarsApi()
        api.loadMovies()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe ({ movie ->
                movies.add("${movie.title} -- ${movie.episodeId}")
            }, { e ->
                e.printStackTrace()
            },{
                movieAdapter.notifyDataSetChanged()
            })
    }
}
Perceba que estamos invocando o método loadMovies() da nossa API. Como vimos anteriormente, esse método retorna um Observable, ou seja, um observável. Nossa tela observará esse objeto, então ela será um Observer. Estamos dizendo que queremos que esse objeto seja criado em background na thread de I/O usando o método subscribeOn(Schedulers.io()). Ele será criado em background, mas queremos observá-lo na main thread do Android, o que nos permitirá atualizar a tela. Ao chamarmos o método subscribe, temos 3 expressões lambda: onNext, que é chamado a cada novo objeto Movie retornado; onError disparado se algum erro ocorrer; e o onCompleted quando a sequência de objetos termina.
No onNext estamos adicionando os filmes na lista (em formato de string para simplificar) e no onCompleted estamos atualizando o adapter para exibir a listagem na tela.
Execute a aplicação e você deverá ver a lista de filmes.

Mas cada filme não deveria ter os seus respectivos personagens?

Sim. Mas para uma tela de listagem isso demora um bocado, pois cada filme tem vários personagens. Então seria melhor na tela de detalhe exibir os personagens. Mas fiquei curioso em saber como fazer isso com RX e resolvi fazer o teste. Vamos voltar ao arquivo StarWarsApi.kt e adicione o seguinte método.
fun loadMoviesFull(): Observable<Movie> {
  return service.listMovies()
      .flatMap { filmResults -> Observable.from(filmResults.results) }
      .flatMap { film ->
          Observable.zip(
              Observable.just(Movie(film.title, film.episodeId, ArrayList<Character>())),
              Observable.from(film.personUrls)
                  .flatMap { personUrl ->
                      service.loadPerson(Uri.parse(personUrl).lastPathSegment)
                  }
                  .map { person ->
                      Character(person!!.name, person.gender)
                  }
                  .toList(), 
                  { movie, characters ->
                      movie.characters.addAll(characters)
                      movie
                  })
      }
}
Olha que loucura isso! :)
Fazemos a requisição da lista de filmes, e para cada filme temos que pegar a lista de URLs dos personagens e apenas quando cada objeto filme estiver completo, é passamos para o próximo. Para fazer isso, utilizamos o operador zip(), pois ele junta o resultado de dois Observables e retorna um novo Observable.
Podemos testar isso agora na nossa Activity.
api.loadMoviesFull()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe ({
            movie ->
            movies.add("${movie.title} -- ${movie.episodeId}\n ${movie.characters.toString() }")
        }, {
            e ->
            e.printStackTrace()
        },{
            movieAdapter?.notifyDataSetChanged()
        })
Na listagem deve aparecer o filme e os respectivos personagens. Essa requisição deve demorar vários segundos.

Fazendo cache!

Alguns personagens aparecem em vários filmes. Por isso seria interessante fazermos cache dos dados desses personagens para não fazermos requisições desnecessárias. Vamos fazer um pequeno ajuste no StarWarsApi.kt.
var peopleCache = mutableMapOf<String, Person>()

fun loadMoviesFull(): Observable<Movie> {
  return service.listMovies()
      .flatMap { filmResults -> Observable.from(filmResults.results) }
      .flatMap { film ->
          val movieObj = Movie(film.title, film.episodeId, ArrayList<Character>())
          Observable.zip(
              Observable.just(movieObj),
              Observable.from(film.personUrls)
                  .flatMap { personUrl ->
                      Observable.concat(
                          getCache(personUrl),
                          service.loadPerson(Uri.parse(personUrl).lastPathSegment)
                              .doOnNext { person ->
                                  peopleCache.put(personUrl, person)
                              }
                          ).first()
                  }
                  .map { person ->
                      Character(person!!.name, person.gender)
                  }.toList(), 
              { movie, characters ->
                  movie.characters.addAll(characters)
                  movie
              })
      }
}

private fun getCache(personUrl : String) : Observable<Person?>? {
    return Observable.from(peopleCache.keys)
        .filter { key ->
            key == personUrl
        }
        .map { key ->
            peopleCache[key]
        }
}
O atributo peopleCache armazena as instâncias de Person. Então na hora que estamos varrendo a lista de personagens utilizamos o operador concat().first() para pegar o primeiro objeto do cache (se existir) ou da API. Quando buscamos da API, adicionamos o objeto no cache, isso é feito no método doOnNext(). Agora estamos fazendo o cache em memória. Mas poderíamos (e deveríamos) fazer em disco.
Como comentei anteriormente, a implementação sem cache demora bastante (uns 30 segundos), mas essa implementação com cache foi bem melhor. Mesmo assim, acho que não seria legal esperar esse tempo todo para trazer a listagem. Seria melhor exibir a listagem de personagens na tela de detalhe de um único filme. Entretanto foi interessante para explorar o potencial do RX com múltiplas requisições.

Ao terminar de escrever o post, notei que tinha muita informação, então resolvi fazer um vídeo mostrado passo a passo a construção do exemplo e tentando explicar melhor a implementação. Espero que gostem :)



Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Referências

Site oficial do Kotlin
https://kotlinlang.org/

Site oficial do RXJava
https://github.com/ReactiveX/RxJava

StarWars API
https://swapi.co

Apresentações Ubiratan Soares
Programação Reativa Funcional com RxJava
Vídeo: https://www.youtube.com/watch?v=0FpphC6hL5I
Slides: https://speakerdeck.com/ubiratansoares/rxjava-for-android
Refactoring for RxJava
Vídeo: https://www.youtube.com/watch?v=391H38-7JYk
Sllides: https://speakerdeck.com/ubiratansoares/refactoring-for-rxjava

Dan Lew cache in RX
http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/

Livro de RX
http://www.oreilly.com/programming/free/rxjava-for-android-app-development.csp

Livro de Kotlin
https://antonioleiva.com/kotlin/