SlideShare a Scribd company logo
Aplicações assíncronas no Android com

Coroutines & Jetpack
Nelson Glauber
@nglauber
• Carregar arquivo de layout
• Desenhar as views
• Tratar os eventos de UI
• …
A main thread do Android
• O processamento desses eventos deve ocorrer em:
• Menos de 16ms para devices com taxas de atualização
de 60Hz
• Menos de 12ms para dispositivos com taxas de 90Hz
• Menos de < 8ms para dispositivos com taxas de 120Hz
A main thread do Android
A main thread do Android
A main thread do Android
Solução?
• AsyncTask 👴
• Thread + Handler 👷
• Loaders (deprecated) 👴
• Volley 🤦🤦
• RxJava 🧐
Async no Android
Coroutines
• Essencialmente, coroutines são light-weight threads.
• Fácil de usar (sem mais “callbacks hell” e/ou centenas de
operadores).
• Úteis para qualquer tarefa computacional mais onerosa
(como operações de I/O).
• Permite a substituição de callbacks por operações
assíncronas.
Coroutines
Dependências
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"
...
}
• Suspending functions são o centro de tudo em Coroutines.

• São funções que podem ser pausadas e retomadas após algum
tempo. 

• Podem executar longas tarefas e aguardar o resultado sem bloquear a
thread atual.

• A sintaxe é idêntica a uma função “normal”, exceto pela adição da
palavra reservada suspend.

• Por si só, uma suspending function não executa em uma thread
separada.

• Só pode ser chamada a partir de outra suspending function.
suspend
import kotlinx.coroutines.delay
class Calculator {
suspend fun sum(a: Int, b: Int): Int {
delay(5_000)
return a + b
}
}
import kotlinx.coroutines.delay
class Calculator {
suspend fun sum(a: Int, b: Int): Int {
delay(5_000)
return a + b
}
}
import kotlinx.coroutines.runBlocking
import org.junit.*
class CalculatorUnitTest {
@Test
fun sum_isCorrect() = runBlocking {
val calc = Calculator()
assertEquals(4, calc.sum(2, 2))
}
}
• Job
• Context
• Scope
• Dispatcher
Principais Classes
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
Job
• Um Job representa uma tarefa ou
conjunto de tarefas em execução.
• Pode possuir “filhos”.
• A função launch retorna um Job.
• Pode ser cancelado usando a função
cancel.
• Possui um ciclo de vida (novo, ativo,
completo ou cancelado)
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
Dispatcher
• Define o pool de threads 

onde a coroutine executará.
• Default: para processos 

que usam a CPU mais
intensamente.
• IO: para tarefas de rede 

ou arquivos. O pool de
threads é compartilhado
com o dispatcher
Default.
• Main - main thread do
Android.
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
Context
• A interface CoroutineContext
Representa o conjunto de atributos
que configuram uma coroutine.
• Pode definir a política de threading;
job raiz; tratamento de exceções;
nome da coroutine (debug).
• Uma coroutine herda o contexto do
pai.
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
Scope
• Uma coroutine sempre roda
em um escopo.
• Serve como uma espécie de
ciclo de vida para um
conjunto de coroutines.
• Permite um maior controle
das tarefas em execução.
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
class MainActivity : AppCompatActivity() {
private val job = Job()
private val coroutineScope =
CoroutineScope(job + Dispatchers.Main)
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
...
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = BookHttp.loadBooks()
// update the UI using books
}
}
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:117)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:105)
at java.net.InetAddress.getAllByName(InetAddress.java:1154)
at com.android.okhttp.Dns$1.lookup(Dns.java:39)
fun callWebService() {
coroutineScope.launch(Dispatchers.IO){
txtOutput.text = ""
val books = BookHttp.loadBooks()
// update the UI using books
}
}
FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: br.com.nglauber.coroutinesdemo, PID: 26507
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that
created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
fun callWebService() {
coroutineScope.launch {
txtOutput.text = ""
val books = withContext(Dispatchers.IO) {
BookHttp.loadBooks()
}
// update the UI using books
}
}
👍
• Suspending functions
• Job
• Context
• Scope
• Dispatcher
Coroutines
Lifecycle
• É possível iniciar coroutines atrelada aos ciclos de vida de
Activity, Fragment e View do Fragment.
• Além da função launch, podemos usar o
launchWhenCreated, launchWhenStarted e
launchWhenResumed.
Lifecycle Scope
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
}
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
...
}
lifecycleScope.launchWhenCreated {
...
}
lifecycleScope.launchWhenStarted {
...
}
lifecycleScope.launchWhenResumed {
...
}
}
}
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
...
}
lifecycleScope.launchWhenCreated {
...
}
lifecycleScope.launchWhenStarted {
...
}
lifecycleScope.launchWhenResumed {
...
}
}
}
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
...
}
lifecycleScope.launchWhenCreated {
...
}
lifecycleScope.launchWhenStarted {
...
}
lifecycleScope.launchWhenResumed {
...
}
}
}
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
...
}
}
}
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
...
}
}
}
Ambos os escopos são
cancelados automaticamente.

🎉🥳
ViewModel




A classe ViewModel possui agora a propriedade viewModelScope.
ViewModel Scope
dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
}
class BookListViewModel: ViewModel() {
private val _state = MutableLiveData<State>()
val state: LiveData<State>
get() = _state
fun search(query: String) {
viewModelScope.launch {
_state.value = State.StateLoading
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(query)
}
_state.value = if (result?.items != null) {
State.StateLoaded(result.items)
} else {
State.StateError(Exception("Error"), false)
}
}
}
}
class BookListViewModel: ViewModel() {
private val _state = MutableLiveData<State>()
val state: LiveData<State>
get() = _state
fun search(query: String) {
viewModelScope.launch {
_state.value = State.StateLoading
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(query)
}
_state.value = if (result?.items != null) {
State.StateLoaded(result.items)
} else {
State.StateError(Exception("Error"), false)
}
}
}
}
class BookListViewModel: ViewModel() {
private val _state = MutableLiveData<State>()
val state: LiveData<State>
get() = _state
fun search(query: String) {
viewModelScope.launch {
_state.value = State.StateLoading
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(query)
}
_state.value = if (result?.items != null) {
State.StateLoaded(result.items)
} else {
State.StateError(Exception("Error"), false)
}
}
}
}
class BookListViewModel: ViewModel() {
private var query = MutableLiveData<String>()
val state = query.switchMap {
liveData {
emit(State.StateLoading)
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(it)
}
emit(
if (result?.items != null) State.StateLoaded(result.items)
else State.StateError(Exception("Error"), false)
)
}
}
fun search(query: String) {
this.query.value = query
}
}
class BookListViewModel: ViewModel() {
private var query = MutableLiveData<String>()
val state = query.switchMap {
liveData {
emit(State.StateLoading)
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(it)
}
emit(
if (result?.items != null) State.StateLoaded(result.items)
else State.StateError(Exception("Error"), false)
)
}
}
fun search(query: String) {
this.query.value = query
}
}
class BookListViewModel: ViewModel() {
private var query = MutableLiveData<String>()
val state = query.switchMap {
liveData {
emit(State.StateLoading)
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(it)
}
emit(
if (result?.items != null) State.StateLoaded(result.items)
else State.StateError(Exception("Error"), false)
)
}
}
fun search(query: String) {
this.query.value = query
}
}
class BookListViewModel: ViewModel() {
private var query = MutableLiveData<String>()
val state = query.switchMap {
liveData {
emit(State.StateLoading)
val result = withContext(Dispatchers.IO) {
BookHttp.searchBook(it)
}
emit(
if (result?.items != null) State.StateLoaded(result.items)
else State.StateError(Exception("Error"), false)
)
}
}
fun search(query: String) {
this.query.value = query
}
}
class BookListActivity : AppCompatActivity(R.layout.activity_book_list) {
private val viewModel: BookListViewModel …
override fun onCreate(savedInstanceState: Bundle?) {
viewModel.state.observe(this, Observer { state ->
when (state) {
is BookListViewModel.State.StateLoading -> ...
is BookListViewModel.State.StateLoaded -> ...
is BookListViewModel.State.StateError -> ...
}
})
}
...
class BookListActivity : AppCompatActivity(R.layout.activity_book_list) {
private val viewModel: BookListViewModel …
override fun onCreate(savedInstanceState: Bundle?) {
viewModel.state.observe(this, Observer { state ->
when (state) {
is BookListViewModel.State.StateLoading -> ...
is BookListViewModel.State.StateLoaded -> ...
is BookListViewModel.State.StateError -> ...
}
})
}
...
WorkManager
WorkManager
dependencies {
def work_version = "2.3.4"
implementation "androidx.work:work-runtime-ktx:$work_version"
}
class MyWork(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// Do something
}
}
• Lifecycle provê um lifecycleScope para Activity e
Fragment (e a view do Fragment).
• ViewModel possui a propriedade viewModelScope.
• WorkManager disponibiliza a classe CoroutineWorker.
Jetpack + Coroutines
Coroutines - Parte 2
• As duas formas de iniciar uma coroutine são:
• A função launch é uma “fire and forget”  que significa
que não retornará o resultado para que a chamou (mas
retornará um Job).
• A função async retorna um objeto Deferred que
permite obter o seu resultado.
Iniciando uma coroutine
launch
launch {
txtOutput.text = ""
val time = measureTimeMillis {
val one = withContext(Dispatchers.IO) { loadFirstNumber() }
val two = withContext(Dispatchers.IO) { loadSecondNumber() }
addTextToTextView("The answer is ${one + two}")
}
addTextToTextView("Completed in $time ms")
}
The answer is 42

Completed in 2030 ms
async
launch {
txtOutput.text = ""
val time = measureTimeMillis {
val one = async(Dispatchers.IO) { loadFirstNumber() }
val two = async(Dispatchers.IO) { loadSecondNumber() }
val s = one.await() + two.await()
addTextToTextView("The answer is $s")
}
addTextToTextView("Completed in $time ms")
}
async
launch {
txtOutput.text = ""
val time = measureTimeMillis {
val one = async(Dispatchers.IO) { loadFirstNumber() }
val two = async(Dispatchers.IO) { loadSecondNumber() }
val s = one.await() + two.await()
addTextToTextView("The answer is $s")
}
addTextToTextView("Completed in $time ms")
}
async
launch {
txtOutput.text = ""
val time = measureTimeMillis {
val one = async(Dispatchers.IO) { loadFirstNumber() }
val two = async(Dispatchers.IO) { loadSecondNumber() }
val s = one.await() + two.await()
addTextToTextView("The answer is $s")
}
addTextToTextView("Completed in $time ms")
}
async
The answer is 42

Completed in 1038 ms
launch {
txtOutput.text = ""
val time = measureTimeMillis {
val one = async(Dispatchers.IO) { loadFirstNumber() }
val two = async(Dispatchers.IO) { loadSecondNumber() }
val s = one.await() + two.await()
addTextToTextView("The answer is $s")
}
addTextToTextView("Completed in $time ms")
}
• O tratamento de exceções é simples, basta tratar no lugar
certo!
• A falha de um Job cancelará o seu “pai" e os demais
“filhos"
• As exceções não tratadas são propagadas para o Job do
escopo.
• Um escopo cancelado não poderá iniciar coroutines.
Exceptions
Exceptions
launch {
txtOutput.text = ""
try {
val result = methodThatThrowsException()
addTextToTextView("Ok $result")
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
👍
Exceptions
launch {
txtOutput.text = ""
try {
launch {
val result = methodThatThrowsException()
addTextToTextView("Ok $result")
}
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
Exceptions
launch {
txtOutput.text = ""
try {
launch {
val result = methodThatThrowsException()
addTextToTextView("Ok $result")
}
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
Exceptions
launch {
txtOutput.text = ""
try {
launch {
val result = methodThatThrowsException()
addTextToTextView("Ok $result")
}
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
Main 

Thread
Flow
launch
Job
Exceptions
launch {
txtOutput.text = ""
try {
launch {
val result = methodThatThrowsException()
addTextToTextView("Ok $result")
}
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
Main 

Thread
Flow
launch
Job
launch
Job
Exceptions
launch {
txtOutput.text = ""
launch {
try {
val result = methodThatThrowsException()
addTextToTextView("Ok $result")
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
}
Exceptions
launch {
txtOutput.text = ""
val task = async { methodThatThrowsException() }
try {
val result = task.await()
addTextToTextView("Ok $result")
} catch (e: Exception) {
addTextToTextView("Error! ${e.message}")
}
}
Exceptions
launch {
txtOutput.text = ""
val task = async {
try {
methodThatThrowsException()
} catch (e: Exception) {
"Error! ${e.message}"
}
}
val result = task.await()
addTextToTextView("Ok $result")
}
Exceptions
launch {
txtOutput.text = ""
val task = async(SupervisorJob(job)) {
methodThatThrowsException()
}
try {
addTextToTextView("Ok ${task.await()}")
} catch (e: Throwable) {
addTextToTextView("Error! ${e.message}")
}
}
Exceptions
launch {
txtOutput.text = ""
try {
coroutineScope {
val task = async {
methodThatThrowsException()
}
addTextToTextView("Ok ${task.await()}")
}
} catch (e: Throwable) {
addTextToTextView("Erro! ${e.message}")
}
}
Exceptions
launch {
txtOutput.text = ""
supervisorScope {
val task = async { methodThatThrowsException() }
try {
addTextToTextView("Ok ${task.await()}")
} catch (e: Throwable) {
addTextToTextView(“Error! ${e.message}")
}
}
}
• Para cancelar um job, basta chamar o método cancel.
• Uma vez cancelado o job não pode ser reusado.
• Para cancelar os jobs filhos, use cancelChildren.
• A propriedade isActive indica que o job está em
execução, isCancelled se a coroutine foi cancelada, e
isCompleted terminou sua execução.
Cancelamento
• Executa uma coroutine levantando uma
TimeoutCancellationException caso sua duração
exceda o tempo especificado.

• Uma vez que o cancelamento é apenas uma exceção, é
possível trata-la facilmente.

• É possível usar a função withTimeoutOrNull que é
similar a withTimeout, mas retorna null ao invés de
levantar a exceção.
withTimeout
withTimeout
launch {
txtOutput.text = ""
try {
val s = withTimeout(1300L) {
withContext(Dispatchers.Default) {
aLongOperation()
}
}
txtOutput.text = "Result: $s..."
} catch (e: TimeoutCancellationException) {
txtOutput.text = "Exception! ${e.message}"
}
}
withTimeout
launch {
txtOutput.text = ""
try {
val s = withTimeout(1300L) {
withContext(Dispatchers.Default) {
aLongOperation()
}
}
txtOutput.text = "Result: $s..."
} catch (e: TimeoutCancellationException) {
txtOutput.text = "Exception! ${e.message}"
}
}
withTimeoutOrNull
launch {
txtOutput.text = ""
val task = async(Dispatchers.Default) {
aLongOperation()
}
val result = withTimeoutOrNull(1300L) { task.await() }
txtOutput.text = "Result: $result"
}
• Nos bastidores, uma suspending function é convertida pelo
compilador para uma função (de mesmo nome) que recebe um
objeto do tipo Continuation.

fun sum(a: Int, b: Int, Continuation<Int>)
• Continuation é uma interface que contém duas funções que
são invocadas para continuar com a execução da coroutine
(normalmente retornando um valor) ou levantar uma exceção
caso algum erro ocorra.

interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
Convertendo Callbacks
Convertendo Callbacks
val db = FirebaseFirestore.getInstance()
db.collection("users")
.add(user)
.addOnSuccessListener { documentReference ->
documentReference.id // Success! Do something with the id
}
.addOnFailureListener { e ->
// Error
}
Convertendo Callbacks
suspend fun addUser(user: User): String =
suspendCoroutine { continuation ->
db.collection("users")
.add(user)
.addOnSuccessListener { documentReference ->
continuation.resume(documentReference.id)
}
.addOnFailureListener { e ->
continuation.resumeWithException(e)
}
}
Convertendo Callbacks
suspend fun addUser(user: User): String =
suspendCoroutine { continuation ->
db.collection("users")
.add(user)
.addOnSuccessListener { documentReference ->
continuation.resume(documentReference.id)
}
.addOnFailureListener { e ->
continuation.resumeWithException(e)
}
}
Convertendo Callbacks
suspend fun addUser(user: User): String =
suspendCoroutine { continuation ->
db.collection("users")
.add(user)
.addOnSuccessListener { documentReference ->
continuation.resume(documentReference.id)
}
.addOnFailureListener { e ->
continuation.resumeWithException(e)
}
}
Convertendo Callbacks
suspend fun addUser(user: User): String =
suspendCoroutine { continuation ->
db.collection("users")
.add(user)
.addOnSuccessListener { documentReference ->
continuation.resume(documentReference.id)
}
.addOnFailureListener { e ->
continuation.resumeWithException(e)
}
}
Convertendo Callbacks
launch {
try {
val userId = repository.addUser(newUser)
// do something
} catch(e: Exception){
// handle error
}
}
• launch (fire-and-forget) e async (para obter um resultado).
• Trate as exceções no launch ou no async. Ou use
SupervisorJob, SupervisorScope ou
supervisorScope.
• cancel ou cancelChildren para cancelar o Job ou os jobs
filhos.
• withTimeout ou withTimeoutOrNull.
• Toda suspend function é convertida em um callback usando a
interface Continuation.
Coroutines - Parte 2
“Reactive Coroutines”
• Flow é uma abstração de um cold stream.
• Nada é executado/emitido até que algum consumidor se
registre no fluxo.
• Possui diversos operadores como no RxJava.
Flow
@FlowPreview
public interface Flow<out T> {
public suspend fun collect(collector: FlowCollector<T>)
}
@FlowPreview
public interface FlowCollector<in T> {
public suspend fun emit(value: T)
}
val intFlow = flow {
for (i in 0 until 10) {
emit(i) //calls emit directly from the body of a FlowCollector
}
}
launch {
intFlow.collect { number ->
addTextToTextView("$numbern")
}
addTextToTextView("DONE!")
}
launch {
(0..100).asFlow()
.map { it * it }
.filter { it % 4 == 0 } // here and above is on IO thread pool
.flowOn(Dispatchers.IO) // 👆change the upstream Dispatcher
.map { it * 2 }
.flowOn(Dispatchers.Main)
.onStart { }
.onEach { }
.onCompletion { }
.collect { number ->
addTextToTextView("$numbern")
}
}
class NumberFlow {
private var currentValue = 0
private val numberChannel = BroadcastChannel<Int>(10)
fun getFlow(): Flow<Int> = numberChannel.asFlow()
suspend fun sendNext() {
numberChannel.send(currentValue++)
}
fun close() = numberChannel.close()
}
class NumberFlow {
private var currentValue = 0
private val numberChannel = BroadcastChannel<Int>(10)
fun getFlow(): Flow<Int> = numberChannel.asFlow()
suspend fun sendNext() {
numberChannel.send(currentValue++)
}
fun close() = numberChannel.close()
}
class NumberFlow {
private var currentValue = 0
private val numberChannel = BroadcastChannel<Int>(10)
fun getFlow(): Flow<Int> = numberChannel.asFlow()
suspend fun sendNext() {
numberChannel.send(currentValue++)
}
fun close() = numberChannel.close()
}
class NumberFlow {
private var currentValue = 0
private val numberChannel = BroadcastChannel<Int>(10)
fun getFlow(): Flow<Int> = numberChannel.asFlow()
suspend fun sendNext() {
numberChannel.send(currentValue++)
}
fun close() = numberChannel.close()
}
class FlowActivity : AppCompatActivity(R.layout.activity_flow) {
private val sender = NumberFlow()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
btnProduce.setOnClickListener {
lifecycleScope.launch {
sender.sendNext()
}
}
lifecycleScope.launch {
sender.getFlow().collect {
txtOutput.append("Number: $it n")
}
}
}
override fun onDestroy() {
super.onDestroy()
sender.close()
}
}
class FlowActivity : AppCompatActivity(R.layout.activity_flow) {
private val sender = NumberFlow()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
btnProduce.setOnClickListener {
lifecycleScope.launch {
sender.sendNext()
}
}
lifecycleScope.launch {
sender.getFlow().collect {
txtOutput.append("Number: $it n")
}
}
}
override fun onDestroy() {
super.onDestroy()
sender.close()
}
}
class FlowActivity : AppCompatActivity(R.layout.activity_flow) {
private val sender = NumberFlow()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
btnProduce.setOnClickListener {
lifecycleScope.launch {
sender.sendNext()
}
}
lifecycleScope.launch {
sender.getFlow().collect {
txtOutput.append("Number: $it n")
}
}
}
override fun onDestroy() {
super.onDestroy()
sender.close()
}
}
class FlowActivity : AppCompatActivity(R.layout.activity_flow) {
private val sender = NumberFlow()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
btnProduce.setOnClickListener {
lifecycleScope.launch {
sender.sendNext()
}
}
lifecycleScope.launch {
sender.getFlow().collect {
txtOutput.append("Number: $it n")
}
}
}
override fun onDestroy() {
super.onDestroy()
sender.close()
}
}
Callback para Flow
class YourApi {
fun doSomeCall(callback: YourListener<String>) {
// when new value arrives
callback.onNext("New Item")
// when some error happens
callback.onApiError(Exception("Error"))
// when we're done
callback.onComplete()
}
interface YourListener<T> {
fun onNext(value: T)
fun onApiError(t: Throwable)
fun onComplete()
}
}
fun myFlow(): Flow<String> = callbackFlow {
val myCallback = object: YourApi.YourListener<String> {
override fun onNext(value: String) { offer(value) }
override fun onApiError(t: Throwable) { close(t) }
override fun onComplete() { close() }
}
val api = YourApi()
api.doSomeCall(myCallback)
awaitClose { /* do something when the stream is closed */ }
}
fun myFlow(): Flow<String> = callbackFlow {
val myCallback = object: YourApi.YourListener<String> {
override fun onNext(value: String) { offer(value) }
override fun onApiError(t: Throwable) { close(t) }
override fun onComplete() { close() }
}
val api = YourApi()
api.doSomeCall(myCallback)
awaitClose { /* do something when the stream is closed */ }
}
fun myFlow(): Flow<String> = callbackFlow {
val myCallback = object: YourApi.YourListener<String> {
override fun onNext(value: String) { offer(value) }
override fun onApiError(t: Throwable) { close(t) }
override fun onComplete() { close() }
}
val api = YourApi()
api.doSomeCall(myCallback)
awaitClose { /* do something when the stream is closed */ }
}
fun myFlow(): Flow<String> = callbackFlow {
val myCallback = object: YourApi.YourListener<String> {
override fun onNext(value: String) { offer(value) }
override fun onApiError(t: Throwable) { close(t) }
override fun onComplete() { close() }
}
val api = YourApi()
api.doSomeCall(myCallback)
awaitClose { /* do something when the stream is closed */ }
}
Firebase para Flow :)
fun loadBooks(): Flow<List<Book>> =
callbackFlow {
val subscription = db.collection("books")
.addSnapshotListener { snapshot, e ->
if (e != null) {
channel.close(e)
} else {
channel.offer(snapshotToList(it))
}
}
awaitClose { subscription.remove() }
}
Room
Room
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
// 👇 Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
...
}
@Dao
interface BookDao {
@Query("SELECT * FROM book")
suspend fun getAll(): List<Book>
@Query("SELECT * FROM book WHERE id = :id")
suspend fun getBook(id: Long): Book
@Insert
suspend fun insert(book: Book): Long
@Delete
suspend fun delete(book: Book)
}
@Dao
interface BookDao {
@Query("SELECT * FROM book")
fun getAll(): Flow<List<Book>>
@Query("SELECT * FROM book WHERE id = :id")
fun getBook(id: Long): Flow<Book?>
@Insert
suspend fun insert(book: Book): Long
@Delete
suspend fun delete(book: Book)
}
launch {
withContext(Dispatchers.IO) {
val id = dao.insert(
Book(0, "Dominando o Android", "Nelson Glauber")
)
}
// Do UI stuff
}
launch { // Room will run it in the specific context
val id = dao.insert(
Book(0, "Dominando o Android", "Nelson Glauber")
)
// Do UI stuff
}
launch {
dao.getAll().collect { bookList ->
lstBooks.adapter = BookAdapter(context, bookList)
}
}
class BookListViewModel(
repository: BookRepository
): ViewModel() {
val allBooks = repository.getAll().asLiveData()
}
Flow
class BookListViewModel(
repository: BookRepository
): ViewModel() {
val allBooks = liveData {
repository.getAll().collect { list ->
emit(convertEntityList(list))
}
}
}
Testes
Testes de Coroutines
testImplementation "junit:junit:4.12"
testImplementation "androidx.arch.core:core-testing:2.0.0"
testImplementation "io.mockk:mockk:1.9.3"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2"
import kotlinx.coroutines.runBlocking
import org.junit.*
class CalculatorUnitTest {
@Test
fun sum_isCorrect() = runBlocking {
val calc = Calculator()
assertEquals(4, calc.sum(2, 2))
}
}
TestRule
class TestCoroutineRule : TestRule {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
override fun apply(base: Statement,
description: Description?) = object : Statement() {
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain() // reset main dispatcher
testCoroutineScope.cleanupTestCoroutines()
}
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest { block() }
}
TestRule
class TestCoroutineRule : TestRule {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
override fun apply(base: Statement,
description: Description?) = object : Statement() {
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain() // reset main dispatcher
testCoroutineScope.cleanupTestCoroutines()
}
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest { block() }
}
TestRule
class TestCoroutineRule : TestRule {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
override fun apply(base: Statement,
description: Description?) = object : Statement() {
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain() // reset main dispatcher
testCoroutineScope.cleanupTestCoroutines()
}
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest { block() }
}
TestRule
class TestCoroutineRule : TestRule {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
override fun apply(base: Statement,
description: Description?) = object : Statement() {
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain() // reset main dispatcher
testCoroutineScope.cleanupTestCoroutines()
}
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest { block() }
}
TestRule
class TestCoroutineRule : TestRule {
private val testCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
override fun apply(base: Statement,
description: Description?) = object : Statement() {
override fun evaluate() {
Dispatchers.setMain(testCoroutineDispatcher)
base.evaluate()
Dispatchers.resetMain() // reset main dispatcher
testCoroutineScope.cleanupTestCoroutines()
}
}
fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
testCoroutineScope.runBlockingTest { block() }
}
Test class
class ListBooksViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
val testCoroutineRule = TestCoroutineRule()
Test
@Test
fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest {
val books = DataFactory.dummyBookList()
initMocks(books)
bookListViewModel.loadBooks()
coVerify(exactly = 1) { listBooksUseCase.execute() }
val currentState = bookListViewModel.state().value
assertEquals(
ViewState.Status.SUCCESS,
currentState?.status)
assertEquals(
books.map { BookConverter.fromData(it) },
currentState?.data)
}
Test
@Test
fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest {
val books = DataFactory.dummyBookList()
initMocks(books)
bookListViewModel.loadBooks()
coVerify(exactly = 1) { listBooksUseCase.execute() }
val currentState = bookListViewModel.state().value
assertEquals(
ViewState.Status.SUCCESS,
currentState?.status)
assertEquals(
books.map { BookConverter.fromData(it) },
currentState?.data)
}
Test
@Test
fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest {
val books = DataFactory.dummyBookList()
initMocks(books)
bookListViewModel.loadBooks()
coVerify(exactly = 1) { listBooksUseCase.execute() }
val currentState = bookListViewModel.state().value
assertEquals(
ViewState.Status.SUCCESS,
currentState?.status)
assertEquals(
books.map { BookConverter.fromData(it) },
currentState?.data)
}
Test
@Test
fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest {
val books = DataFactory.dummyBookList()
initMocks(books)
bookListViewModel.loadBooks()
coVerify(exactly = 1) { listBooksUseCase.execute() }
val currentState = bookListViewModel.state().value
assertEquals(
ViewState.Status.SUCCESS,
currentState?.status)
assertEquals(
books.map { BookConverter.fromData(it) },
currentState?.data)
}
• Coroutines vêm se tornando a forma de padrão para
realizar código assíncrono no Android.
• Essa é uma recomendação do Google.
• Além do Jetpack, outras bibliotecas estão migrando (ou já
migraram) pra Coroutines (ex: Retrofit, Apollo, MockK, …).
Conclusão
• Android Suspenders (Android Dev Summit 2018)

https://www.youtube.com/watch?v=EOjq4OIWKqM
• Understand Kotlin Coroutines on Android (Google I/O 2019)

https://www.youtube.com/watch?v=BOHK_w09pVA
• Coroutines Guide

https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-
guide.md
• Android Suspenders by Chris Banes (KotlinConf 2018)

https://www.youtube.com/watch?v=P7ov_r1JZ1g
• Room & Coroutines (Florina Muntenescu)

https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5
Referências #1
• Using Kotlin Coroutines in your Android App

https://codelabs.developers.google.com/codelabs/kotlin-coroutines
• Use Kotlin coroutines with Architecture Components

https://developer.android.com/topic/libraries/architecture/coroutines
• Create a Clean-Code App with Kotlin Coroutines and Android Architecture
Components

https://blog.elpassion.com/create-a-clean-code-app-with-kotlin-coroutines-and-
android-architecture-components-f533b04b5431
• Android Coroutine Recipes (Dmytro Danylyk)

https://proandroiddev.com/android-coroutine-recipes-33467a4302e9
• Kotlin Coroutines patterns & anti-patterns

https://proandroiddev.com/kotlin-coroutines-patterns-anti-patterns-f9d12984c68e
Referências #2
• The reason to avoid GlobalScope (Roman Elizarov)

https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc
• WorkManager meets Kotlin (Pietro Maggi)

https://medium.com/androiddevelopers/workmanager-meets-kotlin-b9ad02f7405e
• Coroutine Context and Scope (Roman Elizarov)

https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055
• Easy Coroutines in Android: viewModelScope (Manuel Vivo)

https://medium.com/androiddevelopers/easy-coroutines-in-android-
viewmodelscope-25bffb605471
• Exceed the Android Speed Limit

https://medium.com/androiddevelopers/exceed-the-android-speed-limit-
b73a0692abc1
Referências #3
• An Early look at Kotlin Coroutine’s Flow

https://proandroiddev.com/an-early-look-at-kotlin-coroutines-
flow-62e46baa6eb0
• Coroutines on Android (Sean McQuillan)

https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-
background-3e0e54d20bb
• Kotlin Flows and Coroutines (Roman Elizarov)

https://medium.com/@elizarov/kotlin-flows-and-coroutines-256260fb3bdb
• Simple design of Kotlin Flow (Roman Elizarov)

https://medium.com/@elizarov/simple-design-of-kotlin-flow-4725e7398c4c
• React Streams and Kotlin Flows (Roman Elizarov)

https://medium.com/@elizarov/reactive-streams-and-kotlin-flows-bfd12772cda4
Referências #4
• KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina
Muntenescu & Manuel Vivo

https://www.youtube.com/watch?v=w0kfnydnFWI
• LiveData with Coroutines and Flow (Android Dev Summit
'19)

https://www.youtube.com/watch?v=B8ppnjGPAGE
• Testing Coroutines on Android (Android Dev Summit '19)

https://www.youtube.com/watch?v=KMb0Fs8rCRs
Referências #5
Obrigado!
Nelson Glauber
@nglauber

More Related Content

What's hot

The State of JavaScript (2015)
The State of JavaScript (2015)The State of JavaScript (2015)
The State of JavaScript (2015)
Domenic Denicola
 
Ruby meets Go
Ruby meets GoRuby meets Go
第4回 g* ワークショップ はじめてみよう! Grailsプラグイン
第4回 g* ワークショップ はじめてみよう! Grailsプラグイン第4回 g* ワークショップ はじめてみよう! Grailsプラグイン
第4回 g* ワークショップ はじめてみよう! Grailsプラグイン
Tsuyoshi Yamamoto
 
Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2
JollyRogers5
 
Get started with YUI
Get started with YUIGet started with YUI
Get started with YUI
Adam Lu
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
RAMBLER&Co
 
Developing JavaScript Widgets
Developing JavaScript WidgetsDeveloping JavaScript Widgets
Developing JavaScript Widgets
Konstantin Käfer
 
Introduction to Nodejs
Introduction to NodejsIntroduction to Nodejs
Introduction to Nodejs
Gabriele Lana
 
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Tsuyoshi Yamamoto
 
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
Tsuyoshi Yamamoto
 
What is nodejs
What is nodejsWhat is nodejs
What is nodejs
JeongHun Byeon
 
Declarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeDeclarative UIs with Jetpack Compose
Declarative UIs with Jetpack Compose
Ramon Ribeiro Rabello
 
Matthew Eernisse, NodeJs, .toster {webdev}
Matthew Eernisse, NodeJs, .toster {webdev}Matthew Eernisse, NodeJs, .toster {webdev}
Matthew Eernisse, NodeJs, .toster {webdev}
.toster
 
rx.js make async programming simpler
rx.js make async programming simplerrx.js make async programming simpler
rx.js make async programming simpler
Alexander Mostovenko
 
introduction to node.js
introduction to node.jsintroduction to node.js
introduction to node.js
orkaplan
 
Using Node.js to Build Great Streaming Services - HTML5 Dev Conf
Using Node.js to  Build Great  Streaming Services - HTML5 Dev ConfUsing Node.js to  Build Great  Streaming Services - HTML5 Dev Conf
Using Node.js to Build Great Streaming Services - HTML5 Dev Conf
Tom Croucher
 
7주 JavaScript 실습
7주 JavaScript 실습7주 JavaScript 실습
7주 JavaScript 실습
지수 윤
 
Taming that client side mess with Backbone.js
Taming that client side mess with Backbone.jsTaming that client side mess with Backbone.js
Taming that client side mess with Backbone.js
Jarod Ferguson
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from Scala
Hermann Hueck
 
Building a real life application in node js
Building a real life application in node jsBuilding a real life application in node js
Building a real life application in node js
fakedarren
 

What's hot (20)

The State of JavaScript (2015)
The State of JavaScript (2015)The State of JavaScript (2015)
The State of JavaScript (2015)
 
Ruby meets Go
Ruby meets GoRuby meets Go
Ruby meets Go
 
第4回 g* ワークショップ はじめてみよう! Grailsプラグイン
第4回 g* ワークショップ はじめてみよう! Grailsプラグイン第4回 g* ワークショップ はじめてみよう! Grailsプラグイン
第4回 g* ワークショップ はじめてみよう! Grailsプラグイン
 
Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2Intro to Reactive Thinking and RxJava 2
Intro to Reactive Thinking and RxJava 2
 
Get started with YUI
Get started with YUIGet started with YUI
Get started with YUI
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
 
Developing JavaScript Widgets
Developing JavaScript WidgetsDeveloping JavaScript Widgets
Developing JavaScript Widgets
 
Introduction to Nodejs
Introduction to NodejsIntroduction to Nodejs
Introduction to Nodejs
 
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
 
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
 
What is nodejs
What is nodejsWhat is nodejs
What is nodejs
 
Declarative UIs with Jetpack Compose
Declarative UIs with Jetpack ComposeDeclarative UIs with Jetpack Compose
Declarative UIs with Jetpack Compose
 
Matthew Eernisse, NodeJs, .toster {webdev}
Matthew Eernisse, NodeJs, .toster {webdev}Matthew Eernisse, NodeJs, .toster {webdev}
Matthew Eernisse, NodeJs, .toster {webdev}
 
rx.js make async programming simpler
rx.js make async programming simplerrx.js make async programming simpler
rx.js make async programming simpler
 
introduction to node.js
introduction to node.jsintroduction to node.js
introduction to node.js
 
Using Node.js to Build Great Streaming Services - HTML5 Dev Conf
Using Node.js to  Build Great  Streaming Services - HTML5 Dev ConfUsing Node.js to  Build Great  Streaming Services - HTML5 Dev Conf
Using Node.js to Build Great Streaming Services - HTML5 Dev Conf
 
7주 JavaScript 실습
7주 JavaScript 실습7주 JavaScript 실습
7주 JavaScript 실습
 
Taming that client side mess with Backbone.js
Taming that client side mess with Backbone.jsTaming that client side mess with Backbone.js
Taming that client side mess with Backbone.js
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from Scala
 
Building a real life application in node js
Building a real life application in node jsBuilding a real life application in node js
Building a real life application in node js
 

Similar to Aplicações assíncronas no Android com
Coroutines & Jetpack

Kotlin from-scratch 3 - coroutines
Kotlin from-scratch 3 - coroutinesKotlin from-scratch 3 - coroutines
Kotlin from-scratch 3 - coroutines
Franco Lombardo
 
droidcon Transylvania - Kotlin Coroutines
droidcon Transylvania - Kotlin Coroutinesdroidcon Transylvania - Kotlin Coroutines
droidcon Transylvania - Kotlin Coroutines
Arthur Nagy
 
Improving app performance with Kotlin Coroutines
Improving app performance with Kotlin CoroutinesImproving app performance with Kotlin Coroutines
Improving app performance with Kotlin Coroutines
Hassan Abid
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
MongoDB
 
.NET Multithreading/Multitasking
.NET Multithreading/Multitasking.NET Multithreading/Multitasking
.NET Multithreading/Multitasking
Sasha Kravchuk
 
[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless
KatyShimizu
 
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
KatyShimizu
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
David Padbury
 
Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!
Stacy Devino
 
JS Essence
JS EssenceJS Essence
JS Essence
Uladzimir Piatryka
 
Docker & ECS: Secure Nearline Execution
Docker & ECS: Secure Nearline ExecutionDocker & ECS: Secure Nearline Execution
Docker & ECS: Secure Nearline Execution
Brennan Saeta
 
JavaScript for real men
JavaScript for real menJavaScript for real men
JavaScript for real men
Ivano Malavolta
 
Concurrency and Thread-Safe Data Processing in Background Tasks
Concurrency and Thread-Safe Data Processing in Background TasksConcurrency and Thread-Safe Data Processing in Background Tasks
Concurrency and Thread-Safe Data Processing in Background Tasks
WO Community
 
OpenStack Horizon: Controlling the Cloud using Django
OpenStack Horizon: Controlling the Cloud using DjangoOpenStack Horizon: Controlling the Cloud using Django
OpenStack Horizon: Controlling the Cloud using Django
David Lapsley
 
Structured concurrency with Kotlin Coroutines
Structured concurrency with Kotlin CoroutinesStructured concurrency with Kotlin Coroutines
Structured concurrency with Kotlin Coroutines
Vadims Savjolovs
 
JS everywhere 2011
JS everywhere 2011JS everywhere 2011
JS everywhere 2011
Oleg Podsechin
 
Binary Studio Academy: Concurrency in C# 5.0
Binary Studio Academy: Concurrency in C# 5.0Binary Studio Academy: Concurrency in C# 5.0
Binary Studio Academy: Concurrency in C# 5.0
Binary Studio
 
Kotlin Coroutines and Rx
Kotlin Coroutines and RxKotlin Coroutines and Rx
Kotlin Coroutines and Rx
Shaul Rosenzwieg
 
Emerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the HorizonEmerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the Horizon
Alex Payne
 
Arquitecturas de microservicios - Medianet Software
Arquitecturas de microservicios   -  Medianet SoftwareArquitecturas de microservicios   -  Medianet Software
Arquitecturas de microservicios - Medianet Software
Ernesto Hernández Rodríguez
 

Similar to Aplicações assíncronas no Android com
Coroutines & Jetpack (20)

Kotlin from-scratch 3 - coroutines
Kotlin from-scratch 3 - coroutinesKotlin from-scratch 3 - coroutines
Kotlin from-scratch 3 - coroutines
 
droidcon Transylvania - Kotlin Coroutines
droidcon Transylvania - Kotlin Coroutinesdroidcon Transylvania - Kotlin Coroutines
droidcon Transylvania - Kotlin Coroutines
 
Improving app performance with Kotlin Coroutines
Improving app performance with Kotlin CoroutinesImproving app performance with Kotlin Coroutines
Improving app performance with Kotlin Coroutines
 
Fun Teaching MongoDB New Tricks
Fun Teaching MongoDB New TricksFun Teaching MongoDB New Tricks
Fun Teaching MongoDB New Tricks
 
.NET Multithreading/Multitasking
.NET Multithreading/Multitasking.NET Multithreading/Multitasking
.NET Multithreading/Multitasking
 
[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless[NDC 2019] Enterprise-Grade Serverless
[NDC 2019] Enterprise-Grade Serverless
 
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
[NDC 2019] Functions 2.0: Enterprise-Grade Serverless
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
 
Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!
 
JS Essence
JS EssenceJS Essence
JS Essence
 
Docker & ECS: Secure Nearline Execution
Docker & ECS: Secure Nearline ExecutionDocker & ECS: Secure Nearline Execution
Docker & ECS: Secure Nearline Execution
 
JavaScript for real men
JavaScript for real menJavaScript for real men
JavaScript for real men
 
Concurrency and Thread-Safe Data Processing in Background Tasks
Concurrency and Thread-Safe Data Processing in Background TasksConcurrency and Thread-Safe Data Processing in Background Tasks
Concurrency and Thread-Safe Data Processing in Background Tasks
 
OpenStack Horizon: Controlling the Cloud using Django
OpenStack Horizon: Controlling the Cloud using DjangoOpenStack Horizon: Controlling the Cloud using Django
OpenStack Horizon: Controlling the Cloud using Django
 
Structured concurrency with Kotlin Coroutines
Structured concurrency with Kotlin CoroutinesStructured concurrency with Kotlin Coroutines
Structured concurrency with Kotlin Coroutines
 
JS everywhere 2011
JS everywhere 2011JS everywhere 2011
JS everywhere 2011
 
Binary Studio Academy: Concurrency in C# 5.0
Binary Studio Academy: Concurrency in C# 5.0Binary Studio Academy: Concurrency in C# 5.0
Binary Studio Academy: Concurrency in C# 5.0
 
Kotlin Coroutines and Rx
Kotlin Coroutines and RxKotlin Coroutines and Rx
Kotlin Coroutines and Rx
 
Emerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the HorizonEmerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the Horizon
 
Arquitecturas de microservicios - Medianet Software
Arquitecturas de microservicios   -  Medianet SoftwareArquitecturas de microservicios   -  Medianet Software
Arquitecturas de microservicios - Medianet Software
 

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
 
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
 
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
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
Nelson Glauber Leal
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
Nelson Glauber Leal
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com 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
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no Android
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
 
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
 
Arquitetando seu app Android com Jetpack
Arquitetando seu app Android com JetpackArquitetando seu app Android com Jetpack
Arquitetando seu app Android com Jetpack
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
 
Arquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com JetpackArquitetando seu aplicativo Android com Jetpack
Arquitetando seu aplicativo Android com Jetpack
 
Desenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos AndroidDesenvolvimento Moderno de Aplicativos Android
Desenvolvimento Moderno de Aplicativos Android
 
Desenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos AndroidDesenvolvimento Moderno de aplicativos Android
Desenvolvimento Moderno de aplicativos Android
 
Turbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com KotlinTurbinando o desenvolvimento Android com Kotlin
Turbinando o desenvolvimento Android com Kotlin
 
Tudo que você precisa saber sobre Constraint Layout
Tudo que você precisa saber sobre Constraint LayoutTudo que você precisa saber sobre Constraint Layout
Tudo que você precisa saber sobre Constraint Layout
 
Persistência de Dados no SQLite com Room
Persistência de Dados no SQLite com RoomPersistência de Dados no SQLite com Room
Persistência de Dados no SQLite com Room
 
The world of Android Animations
The world of Android AnimationsThe world of Android Animations
The world of Android Animations
 
Android Constraint Layout
Android Constraint LayoutAndroid Constraint Layout
Android Constraint Layout
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no Android
 

Recently uploaded

Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...
Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...
Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...
shanihomely
 
Amadeus Travel API, Amadeus Booking API, Amadeus GDS
Amadeus Travel API, Amadeus Booking API, Amadeus GDSAmadeus Travel API, Amadeus Booking API, Amadeus GDS
Amadeus Travel API, Amadeus Booking API, Amadeus GDS
aadhiyaeliza
 
Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...
Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...
Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...
kiara pandey
 
BATber53 AWS Modernize your applications with purpose-built AWS databases
BATber53 AWS Modernize your applications with purpose-built AWS databasesBATber53 AWS Modernize your applications with purpose-built AWS databases
BATber53 AWS Modernize your applications with purpose-built AWS databases
BATbern
 
To Avoid Mistakes When Using Online Attendance Sheets
To Avoid Mistakes When Using Online Attendance SheetsTo Avoid Mistakes When Using Online Attendance Sheets
To Avoid Mistakes When Using Online Attendance Sheets
Task Tracker
 
Tour and travel website management in odoo,
Tour and travel website management in odoo,Tour and travel website management in odoo,
Tour and travel website management in odoo,
Axis Technolabs
 
Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...
Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...
Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...
dream girl
 
Wired_2.0_Create_AmsterdamJUG_09072024.pptx
Wired_2.0_Create_AmsterdamJUG_09072024.pptxWired_2.0_Create_AmsterdamJUG_09072024.pptx
Wired_2.0_Create_AmsterdamJUG_09072024.pptx
SimonedeGijt
 
TEQnation 2024: Sustainable Software: May the Green Code Be with You
TEQnation 2024: Sustainable Software: May the Green Code Be with YouTEQnation 2024: Sustainable Software: May the Green Code Be with You
TEQnation 2024: Sustainable Software: May the Green Code Be with You
marcofolio
 
GT degree offer diploma Transcript
GT degree offer diploma TranscriptGT degree offer diploma Transcript
GT degree offer diploma Transcript
attueb
 
High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...
High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...
High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...
singhlata50dh
 
Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...
Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...
Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...
902basic
 
ERP Software Solutions Provider in Coimbatore
ERP Software Solutions Provider in CoimbatoreERP Software Solutions Provider in Coimbatore
ERP Software Solutions Provider in Coimbatore
Nextskill Technologies
 
SAP implementation steps PDF - Zyple Software
SAP implementation steps PDF - Zyple SoftwareSAP implementation steps PDF - Zyple Software
SAP implementation steps PDF - Zyple Software
Zyple Software
 
bangalore Girls call 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
bangalore Girls call  👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Deliverybangalore Girls call  👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
bangalore Girls call 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
sunilverma7884
 
Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...
Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...
Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...
87tomato
 
A Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdf
A Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdfA Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdf
A Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdf
kalichargn70th171
 
Top Chinese Government-backed APT Groups
Top Chinese Government-backed APT GroupsTop Chinese Government-backed APT Groups
Top Chinese Government-backed APT Groups
SOCRadar
 
Il Data Streaming per un’AI real-time di nuova generazione
Il Data Streaming per un’AI real-time di nuova generazioneIl Data Streaming per un’AI real-time di nuova generazione
Il Data Streaming per un’AI real-time di nuova generazione
confluent
 
AI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdf
AI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdfAI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdf
AI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdf
Daniel Zivkovic
 

Recently uploaded (20)

Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...
Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...
Russian Girls Call Mumbai 🎈🔥9930687706 🔥💋🎈 Provide Best And Top Girl Service ...
 
Amadeus Travel API, Amadeus Booking API, Amadeus GDS
Amadeus Travel API, Amadeus Booking API, Amadeus GDSAmadeus Travel API, Amadeus Booking API, Amadeus GDS
Amadeus Travel API, Amadeus Booking API, Amadeus GDS
 
Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...
Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...
Celebrity Girls Call Mumbai 9930687706 Unlimited Short Providing Girls Servic...
 
BATber53 AWS Modernize your applications with purpose-built AWS databases
BATber53 AWS Modernize your applications with purpose-built AWS databasesBATber53 AWS Modernize your applications with purpose-built AWS databases
BATber53 AWS Modernize your applications with purpose-built AWS databases
 
To Avoid Mistakes When Using Online Attendance Sheets
To Avoid Mistakes When Using Online Attendance SheetsTo Avoid Mistakes When Using Online Attendance Sheets
To Avoid Mistakes When Using Online Attendance Sheets
 
Tour and travel website management in odoo,
Tour and travel website management in odoo,Tour and travel website management in odoo,
Tour and travel website management in odoo,
 
Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...
Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...
Russian Girls Call Mumbai 🛵🚡9833363713 💃 Choose Best And Top Girl Service And...
 
Wired_2.0_Create_AmsterdamJUG_09072024.pptx
Wired_2.0_Create_AmsterdamJUG_09072024.pptxWired_2.0_Create_AmsterdamJUG_09072024.pptx
Wired_2.0_Create_AmsterdamJUG_09072024.pptx
 
TEQnation 2024: Sustainable Software: May the Green Code Be with You
TEQnation 2024: Sustainable Software: May the Green Code Be with YouTEQnation 2024: Sustainable Software: May the Green Code Be with You
TEQnation 2024: Sustainable Software: May the Green Code Be with You
 
GT degree offer diploma Transcript
GT degree offer diploma TranscriptGT degree offer diploma Transcript
GT degree offer diploma Transcript
 
High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...
High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...
High Girls Call Chennai 000XX00000 Provide Best And Top Girl Service And No1 ...
 
Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...
Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...
Private Girls Call Navi Mumbai 🛵🚡9820252231 💃 Choose Best And Top Girl Servic...
 
ERP Software Solutions Provider in Coimbatore
ERP Software Solutions Provider in CoimbatoreERP Software Solutions Provider in Coimbatore
ERP Software Solutions Provider in Coimbatore
 
SAP implementation steps PDF - Zyple Software
SAP implementation steps PDF - Zyple SoftwareSAP implementation steps PDF - Zyple Software
SAP implementation steps PDF - Zyple Software
 
bangalore Girls call 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
bangalore Girls call  👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Deliverybangalore Girls call  👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
bangalore Girls call 👀 XXXXXXXXXXX 👀 Rs.9.5 K Cash Payment With Room Delivery
 
Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...
Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...
Verified Girls Call Mumbai 👀 9820252231 👀 Cash Payment With Room DeliveryDeli...
 
A Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdf
A Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdfA Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdf
A Step-by-Step Guide to Selecting the Right Automated Software Testing Tools.pdf
 
Top Chinese Government-backed APT Groups
Top Chinese Government-backed APT GroupsTop Chinese Government-backed APT Groups
Top Chinese Government-backed APT Groups
 
Il Data Streaming per un’AI real-time di nuova generazione
Il Data Streaming per un’AI real-time di nuova generazioneIl Data Streaming per un’AI real-time di nuova generazione
Il Data Streaming per un’AI real-time di nuova generazione
 
AI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdf
AI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdfAI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdf
AI - Your Startup Sidekick (Leveraging AI to Bootstrap a Lean Startup).pdf
 

Aplicações assíncronas no Android com
Coroutines & Jetpack

  • 1. Aplicações assíncronas no Android com
 Coroutines & Jetpack Nelson Glauber @nglauber
  • 2. • Carregar arquivo de layout • Desenhar as views • Tratar os eventos de UI • … A main thread do Android
  • 3. • O processamento desses eventos deve ocorrer em: • Menos de 16ms para devices com taxas de atualização de 60Hz • Menos de 12ms para dispositivos com taxas de 90Hz • Menos de < 8ms para dispositivos com taxas de 120Hz A main thread do Android
  • 4. A main thread do Android
  • 5. A main thread do Android
  • 7. • AsyncTask 👴 • Thread + Handler 👷 • Loaders (deprecated) 👴 • Volley 🤦🤦 • RxJava 🧐 Async no Android
  • 9. • Essencialmente, coroutines são light-weight threads. • Fácil de usar (sem mais “callbacks hell” e/ou centenas de operadores). • Úteis para qualquer tarefa computacional mais onerosa (como operações de I/O). • Permite a substituição de callbacks por operações assíncronas. Coroutines
  • 11. • Suspending functions são o centro de tudo em Coroutines. • São funções que podem ser pausadas e retomadas após algum tempo. • Podem executar longas tarefas e aguardar o resultado sem bloquear a thread atual. • A sintaxe é idêntica a uma função “normal”, exceto pela adição da palavra reservada suspend. • Por si só, uma suspending function não executa em uma thread separada. • Só pode ser chamada a partir de outra suspending function. suspend
  • 12. import kotlinx.coroutines.delay class Calculator { suspend fun sum(a: Int, b: Int): Int { delay(5_000) return a + b } }
  • 13. import kotlinx.coroutines.delay class Calculator { suspend fun sum(a: Int, b: Int): Int { delay(5_000) return a + b } } import kotlinx.coroutines.runBlocking import org.junit.* class CalculatorUnitTest { @Test fun sum_isCorrect() = runBlocking { val calc = Calculator() assertEquals(4, calc.sum(2, 2)) } }
  • 14. • Job • Context • Scope • Dispatcher Principais Classes
  • 15. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ...
  • 16. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ... Job • Um Job representa uma tarefa ou conjunto de tarefas em execução. • Pode possuir “filhos”. • A função launch retorna um Job. • Pode ser cancelado usando a função cancel. • Possui um ciclo de vida (novo, ativo, completo ou cancelado)
  • 17. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ... Dispatcher • Define o pool de threads 
 onde a coroutine executará. • Default: para processos 
 que usam a CPU mais intensamente. • IO: para tarefas de rede 
 ou arquivos. O pool de threads é compartilhado com o dispatcher Default. • Main - main thread do Android.
  • 18. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ... Context • A interface CoroutineContext Representa o conjunto de atributos que configuram uma coroutine. • Pode definir a política de threading; job raiz; tratamento de exceções; nome da coroutine (debug). • Uma coroutine herda o contexto do pai.
  • 19. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ... Scope • Uma coroutine sempre roda em um escopo. • Serve como uma espécie de ciclo de vida para um conjunto de coroutines. • Permite um maior controle das tarefas em execução.
  • 20. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ...
  • 21. class MainActivity : AppCompatActivity() { private val job = Job() private val coroutineScope = CoroutineScope(job + Dispatchers.Main) override fun onDestroy() { super.onDestroy() job.cancel() } fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } ...
  • 22. fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } }
  • 23. fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = BookHttp.loadBooks() // update the UI using books } } android.os.NetworkOnMainThreadException at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1513) at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:117) at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:105) at java.net.InetAddress.getAllByName(InetAddress.java:1154) at com.android.okhttp.Dns$1.lookup(Dns.java:39)
  • 24. fun callWebService() { coroutineScope.launch(Dispatchers.IO){ txtOutput.text = "" val books = BookHttp.loadBooks() // update the UI using books } } FATAL EXCEPTION: DefaultDispatcher-worker-1 Process: br.com.nglauber.coroutinesdemo, PID: 26507 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
  • 25. fun callWebService() { coroutineScope.launch { txtOutput.text = "" val books = withContext(Dispatchers.IO) { BookHttp.loadBooks() } // update the UI using books } } 👍
  • 26. • Suspending functions • Job • Context • Scope • Dispatcher Coroutines
  • 28. • É possível iniciar coroutines atrelada aos ciclos de vida de Activity, Fragment e View do Fragment. • Além da função launch, podemos usar o launchWhenCreated, launchWhenStarted e launchWhenResumed. Lifecycle Scope dependencies { ... implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" }
  • 29. class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { ... } lifecycleScope.launchWhenCreated { ... } lifecycleScope.launchWhenStarted { ... } lifecycleScope.launchWhenResumed { ... } } }
  • 30. class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { ... } lifecycleScope.launchWhenCreated { ... } lifecycleScope.launchWhenStarted { ... } lifecycleScope.launchWhenResumed { ... } } }
  • 31. class MyFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { ... } lifecycleScope.launchWhenCreated { ... } lifecycleScope.launchWhenStarted { ... } lifecycleScope.launchWhenResumed { ... } } }
  • 32. class MyFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { ... } } }
  • 33. class MyFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { ... } } } Ambos os escopos são cancelados automaticamente.
 🎉🥳
  • 35. 
 
 A classe ViewModel possui agora a propriedade viewModelScope. ViewModel Scope dependencies { implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" }
  • 36. class BookListViewModel: ViewModel() { private val _state = MutableLiveData<State>() val state: LiveData<State> get() = _state fun search(query: String) { viewModelScope.launch { _state.value = State.StateLoading val result = withContext(Dispatchers.IO) { BookHttp.searchBook(query) } _state.value = if (result?.items != null) { State.StateLoaded(result.items) } else { State.StateError(Exception("Error"), false) } } } }
  • 37. class BookListViewModel: ViewModel() { private val _state = MutableLiveData<State>() val state: LiveData<State> get() = _state fun search(query: String) { viewModelScope.launch { _state.value = State.StateLoading val result = withContext(Dispatchers.IO) { BookHttp.searchBook(query) } _state.value = if (result?.items != null) { State.StateLoaded(result.items) } else { State.StateError(Exception("Error"), false) } } } }
  • 38. class BookListViewModel: ViewModel() { private val _state = MutableLiveData<State>() val state: LiveData<State> get() = _state fun search(query: String) { viewModelScope.launch { _state.value = State.StateLoading val result = withContext(Dispatchers.IO) { BookHttp.searchBook(query) } _state.value = if (result?.items != null) { State.StateLoaded(result.items) } else { State.StateError(Exception("Error"), false) } } } }
  • 39. class BookListViewModel: ViewModel() { private var query = MutableLiveData<String>() val state = query.switchMap { liveData { emit(State.StateLoading) val result = withContext(Dispatchers.IO) { BookHttp.searchBook(it) } emit( if (result?.items != null) State.StateLoaded(result.items) else State.StateError(Exception("Error"), false) ) } } fun search(query: String) { this.query.value = query } }
  • 40. class BookListViewModel: ViewModel() { private var query = MutableLiveData<String>() val state = query.switchMap { liveData { emit(State.StateLoading) val result = withContext(Dispatchers.IO) { BookHttp.searchBook(it) } emit( if (result?.items != null) State.StateLoaded(result.items) else State.StateError(Exception("Error"), false) ) } } fun search(query: String) { this.query.value = query } }
  • 41. class BookListViewModel: ViewModel() { private var query = MutableLiveData<String>() val state = query.switchMap { liveData { emit(State.StateLoading) val result = withContext(Dispatchers.IO) { BookHttp.searchBook(it) } emit( if (result?.items != null) State.StateLoaded(result.items) else State.StateError(Exception("Error"), false) ) } } fun search(query: String) { this.query.value = query } }
  • 42. class BookListViewModel: ViewModel() { private var query = MutableLiveData<String>() val state = query.switchMap { liveData { emit(State.StateLoading) val result = withContext(Dispatchers.IO) { BookHttp.searchBook(it) } emit( if (result?.items != null) State.StateLoaded(result.items) else State.StateError(Exception("Error"), false) ) } } fun search(query: String) { this.query.value = query } }
  • 43. class BookListActivity : AppCompatActivity(R.layout.activity_book_list) { private val viewModel: BookListViewModel … override fun onCreate(savedInstanceState: Bundle?) { viewModel.state.observe(this, Observer { state -> when (state) { is BookListViewModel.State.StateLoading -> ... is BookListViewModel.State.StateLoaded -> ... is BookListViewModel.State.StateError -> ... } }) } ...
  • 44. class BookListActivity : AppCompatActivity(R.layout.activity_book_list) { private val viewModel: BookListViewModel … override fun onCreate(savedInstanceState: Bundle?) { viewModel.state.observe(this, Observer { state -> when (state) { is BookListViewModel.State.StateLoading -> ... is BookListViewModel.State.StateLoaded -> ... is BookListViewModel.State.StateError -> ... } }) } ...
  • 46. WorkManager dependencies { def work_version = "2.3.4" implementation "androidx.work:work-runtime-ktx:$work_version" }
  • 47. class MyWork(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { // Do something } }
  • 48. • Lifecycle provê um lifecycleScope para Activity e Fragment (e a view do Fragment). • ViewModel possui a propriedade viewModelScope. • WorkManager disponibiliza a classe CoroutineWorker. Jetpack + Coroutines
  • 50. • As duas formas de iniciar uma coroutine são: • A função launch é uma “fire and forget”  que significa que não retornará o resultado para que a chamou (mas retornará um Job). • A função async retorna um objeto Deferred que permite obter o seu resultado. Iniciando uma coroutine
  • 51. launch launch { txtOutput.text = "" val time = measureTimeMillis { val one = withContext(Dispatchers.IO) { loadFirstNumber() } val two = withContext(Dispatchers.IO) { loadSecondNumber() } addTextToTextView("The answer is ${one + two}") } addTextToTextView("Completed in $time ms") } The answer is 42
 Completed in 2030 ms
  • 52. async launch { txtOutput.text = "" val time = measureTimeMillis { val one = async(Dispatchers.IO) { loadFirstNumber() } val two = async(Dispatchers.IO) { loadSecondNumber() } val s = one.await() + two.await() addTextToTextView("The answer is $s") } addTextToTextView("Completed in $time ms") }
  • 53. async launch { txtOutput.text = "" val time = measureTimeMillis { val one = async(Dispatchers.IO) { loadFirstNumber() } val two = async(Dispatchers.IO) { loadSecondNumber() } val s = one.await() + two.await() addTextToTextView("The answer is $s") } addTextToTextView("Completed in $time ms") }
  • 54. async launch { txtOutput.text = "" val time = measureTimeMillis { val one = async(Dispatchers.IO) { loadFirstNumber() } val two = async(Dispatchers.IO) { loadSecondNumber() } val s = one.await() + two.await() addTextToTextView("The answer is $s") } addTextToTextView("Completed in $time ms") }
  • 55. async The answer is 42
 Completed in 1038 ms launch { txtOutput.text = "" val time = measureTimeMillis { val one = async(Dispatchers.IO) { loadFirstNumber() } val two = async(Dispatchers.IO) { loadSecondNumber() } val s = one.await() + two.await() addTextToTextView("The answer is $s") } addTextToTextView("Completed in $time ms") }
  • 56. • O tratamento de exceções é simples, basta tratar no lugar certo! • A falha de um Job cancelará o seu “pai" e os demais “filhos" • As exceções não tratadas são propagadas para o Job do escopo. • Um escopo cancelado não poderá iniciar coroutines. Exceptions
  • 57. Exceptions launch { txtOutput.text = "" try { val result = methodThatThrowsException() addTextToTextView("Ok $result") } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } } 👍
  • 58. Exceptions launch { txtOutput.text = "" try { launch { val result = methodThatThrowsException() addTextToTextView("Ok $result") } } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } }
  • 59. Exceptions launch { txtOutput.text = "" try { launch { val result = methodThatThrowsException() addTextToTextView("Ok $result") } } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } }
  • 60. Exceptions launch { txtOutput.text = "" try { launch { val result = methodThatThrowsException() addTextToTextView("Ok $result") } } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } } Main 
 Thread Flow launch Job
  • 61. Exceptions launch { txtOutput.text = "" try { launch { val result = methodThatThrowsException() addTextToTextView("Ok $result") } } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } } Main 
 Thread Flow launch Job launch Job
  • 62. Exceptions launch { txtOutput.text = "" launch { try { val result = methodThatThrowsException() addTextToTextView("Ok $result") } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } } }
  • 63. Exceptions launch { txtOutput.text = "" val task = async { methodThatThrowsException() } try { val result = task.await() addTextToTextView("Ok $result") } catch (e: Exception) { addTextToTextView("Error! ${e.message}") } }
  • 64. Exceptions launch { txtOutput.text = "" val task = async { try { methodThatThrowsException() } catch (e: Exception) { "Error! ${e.message}" } } val result = task.await() addTextToTextView("Ok $result") }
  • 65. Exceptions launch { txtOutput.text = "" val task = async(SupervisorJob(job)) { methodThatThrowsException() } try { addTextToTextView("Ok ${task.await()}") } catch (e: Throwable) { addTextToTextView("Error! ${e.message}") } }
  • 66. Exceptions launch { txtOutput.text = "" try { coroutineScope { val task = async { methodThatThrowsException() } addTextToTextView("Ok ${task.await()}") } } catch (e: Throwable) { addTextToTextView("Erro! ${e.message}") } }
  • 67. Exceptions launch { txtOutput.text = "" supervisorScope { val task = async { methodThatThrowsException() } try { addTextToTextView("Ok ${task.await()}") } catch (e: Throwable) { addTextToTextView(“Error! ${e.message}") } } }
  • 68. • Para cancelar um job, basta chamar o método cancel. • Uma vez cancelado o job não pode ser reusado. • Para cancelar os jobs filhos, use cancelChildren. • A propriedade isActive indica que o job está em execução, isCancelled se a coroutine foi cancelada, e isCompleted terminou sua execução. Cancelamento
  • 69. • Executa uma coroutine levantando uma TimeoutCancellationException caso sua duração exceda o tempo especificado. • Uma vez que o cancelamento é apenas uma exceção, é possível trata-la facilmente. • É possível usar a função withTimeoutOrNull que é similar a withTimeout, mas retorna null ao invés de levantar a exceção. withTimeout
  • 70. withTimeout launch { txtOutput.text = "" try { val s = withTimeout(1300L) { withContext(Dispatchers.Default) { aLongOperation() } } txtOutput.text = "Result: $s..." } catch (e: TimeoutCancellationException) { txtOutput.text = "Exception! ${e.message}" } }
  • 71. withTimeout launch { txtOutput.text = "" try { val s = withTimeout(1300L) { withContext(Dispatchers.Default) { aLongOperation() } } txtOutput.text = "Result: $s..." } catch (e: TimeoutCancellationException) { txtOutput.text = "Exception! ${e.message}" } }
  • 72. withTimeoutOrNull launch { txtOutput.text = "" val task = async(Dispatchers.Default) { aLongOperation() } val result = withTimeoutOrNull(1300L) { task.await() } txtOutput.text = "Result: $result" }
  • 73. • Nos bastidores, uma suspending function é convertida pelo compilador para uma função (de mesmo nome) que recebe um objeto do tipo Continuation.
 fun sum(a: Int, b: Int, Continuation<Int>) • Continuation é uma interface que contém duas funções que são invocadas para continuar com a execução da coroutine (normalmente retornando um valor) ou levantar uma exceção caso algum erro ocorra.
 interface Continuation<in T> { val context: CoroutineContext fun resume(value: T) fun resumeWithException(exception: Throwable) } Convertendo Callbacks
  • 74. Convertendo Callbacks val db = FirebaseFirestore.getInstance() db.collection("users") .add(user) .addOnSuccessListener { documentReference -> documentReference.id // Success! Do something with the id } .addOnFailureListener { e -> // Error }
  • 75. Convertendo Callbacks suspend fun addUser(user: User): String = suspendCoroutine { continuation -> db.collection("users") .add(user) .addOnSuccessListener { documentReference -> continuation.resume(documentReference.id) } .addOnFailureListener { e -> continuation.resumeWithException(e) } }
  • 76. Convertendo Callbacks suspend fun addUser(user: User): String = suspendCoroutine { continuation -> db.collection("users") .add(user) .addOnSuccessListener { documentReference -> continuation.resume(documentReference.id) } .addOnFailureListener { e -> continuation.resumeWithException(e) } }
  • 77. Convertendo Callbacks suspend fun addUser(user: User): String = suspendCoroutine { continuation -> db.collection("users") .add(user) .addOnSuccessListener { documentReference -> continuation.resume(documentReference.id) } .addOnFailureListener { e -> continuation.resumeWithException(e) } }
  • 78. Convertendo Callbacks suspend fun addUser(user: User): String = suspendCoroutine { continuation -> db.collection("users") .add(user) .addOnSuccessListener { documentReference -> continuation.resume(documentReference.id) } .addOnFailureListener { e -> continuation.resumeWithException(e) } }
  • 79. Convertendo Callbacks launch { try { val userId = repository.addUser(newUser) // do something } catch(e: Exception){ // handle error } }
  • 80. • launch (fire-and-forget) e async (para obter um resultado). • Trate as exceções no launch ou no async. Ou use SupervisorJob, SupervisorScope ou supervisorScope. • cancel ou cancelChildren para cancelar o Job ou os jobs filhos. • withTimeout ou withTimeoutOrNull. • Toda suspend function é convertida em um callback usando a interface Continuation. Coroutines - Parte 2
  • 82. • Flow é uma abstração de um cold stream. • Nada é executado/emitido até que algum consumidor se registre no fluxo. • Possui diversos operadores como no RxJava. Flow
  • 83. @FlowPreview public interface Flow<out T> { public suspend fun collect(collector: FlowCollector<T>) } @FlowPreview public interface FlowCollector<in T> { public suspend fun emit(value: T) }
  • 84. val intFlow = flow { for (i in 0 until 10) { emit(i) //calls emit directly from the body of a FlowCollector } } launch { intFlow.collect { number -> addTextToTextView("$numbern") } addTextToTextView("DONE!") }
  • 85. launch { (0..100).asFlow() .map { it * it } .filter { it % 4 == 0 } // here and above is on IO thread pool .flowOn(Dispatchers.IO) // 👆change the upstream Dispatcher .map { it * 2 } .flowOn(Dispatchers.Main) .onStart { } .onEach { } .onCompletion { } .collect { number -> addTextToTextView("$numbern") } }
  • 86. class NumberFlow { private var currentValue = 0 private val numberChannel = BroadcastChannel<Int>(10) fun getFlow(): Flow<Int> = numberChannel.asFlow() suspend fun sendNext() { numberChannel.send(currentValue++) } fun close() = numberChannel.close() }
  • 87. class NumberFlow { private var currentValue = 0 private val numberChannel = BroadcastChannel<Int>(10) fun getFlow(): Flow<Int> = numberChannel.asFlow() suspend fun sendNext() { numberChannel.send(currentValue++) } fun close() = numberChannel.close() }
  • 88. class NumberFlow { private var currentValue = 0 private val numberChannel = BroadcastChannel<Int>(10) fun getFlow(): Flow<Int> = numberChannel.asFlow() suspend fun sendNext() { numberChannel.send(currentValue++) } fun close() = numberChannel.close() }
  • 89. class NumberFlow { private var currentValue = 0 private val numberChannel = BroadcastChannel<Int>(10) fun getFlow(): Flow<Int> = numberChannel.asFlow() suspend fun sendNext() { numberChannel.send(currentValue++) } fun close() = numberChannel.close() }
  • 90. class FlowActivity : AppCompatActivity(R.layout.activity_flow) { private val sender = NumberFlow() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btnProduce.setOnClickListener { lifecycleScope.launch { sender.sendNext() } } lifecycleScope.launch { sender.getFlow().collect { txtOutput.append("Number: $it n") } } } override fun onDestroy() { super.onDestroy() sender.close() } }
  • 91. class FlowActivity : AppCompatActivity(R.layout.activity_flow) { private val sender = NumberFlow() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btnProduce.setOnClickListener { lifecycleScope.launch { sender.sendNext() } } lifecycleScope.launch { sender.getFlow().collect { txtOutput.append("Number: $it n") } } } override fun onDestroy() { super.onDestroy() sender.close() } }
  • 92. class FlowActivity : AppCompatActivity(R.layout.activity_flow) { private val sender = NumberFlow() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btnProduce.setOnClickListener { lifecycleScope.launch { sender.sendNext() } } lifecycleScope.launch { sender.getFlow().collect { txtOutput.append("Number: $it n") } } } override fun onDestroy() { super.onDestroy() sender.close() } }
  • 93. class FlowActivity : AppCompatActivity(R.layout.activity_flow) { private val sender = NumberFlow() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) btnProduce.setOnClickListener { lifecycleScope.launch { sender.sendNext() } } lifecycleScope.launch { sender.getFlow().collect { txtOutput.append("Number: $it n") } } } override fun onDestroy() { super.onDestroy() sender.close() } }
  • 94. Callback para Flow class YourApi { fun doSomeCall(callback: YourListener<String>) { // when new value arrives callback.onNext("New Item") // when some error happens callback.onApiError(Exception("Error")) // when we're done callback.onComplete() } interface YourListener<T> { fun onNext(value: T) fun onApiError(t: Throwable) fun onComplete() } }
  • 95. fun myFlow(): Flow<String> = callbackFlow { val myCallback = object: YourApi.YourListener<String> { override fun onNext(value: String) { offer(value) } override fun onApiError(t: Throwable) { close(t) } override fun onComplete() { close() } } val api = YourApi() api.doSomeCall(myCallback) awaitClose { /* do something when the stream is closed */ } }
  • 96. fun myFlow(): Flow<String> = callbackFlow { val myCallback = object: YourApi.YourListener<String> { override fun onNext(value: String) { offer(value) } override fun onApiError(t: Throwable) { close(t) } override fun onComplete() { close() } } val api = YourApi() api.doSomeCall(myCallback) awaitClose { /* do something when the stream is closed */ } }
  • 97. fun myFlow(): Flow<String> = callbackFlow { val myCallback = object: YourApi.YourListener<String> { override fun onNext(value: String) { offer(value) } override fun onApiError(t: Throwable) { close(t) } override fun onComplete() { close() } } val api = YourApi() api.doSomeCall(myCallback) awaitClose { /* do something when the stream is closed */ } }
  • 98. fun myFlow(): Flow<String> = callbackFlow { val myCallback = object: YourApi.YourListener<String> { override fun onNext(value: String) { offer(value) } override fun onApiError(t: Throwable) { close(t) } override fun onComplete() { close() } } val api = YourApi() api.doSomeCall(myCallback) awaitClose { /* do something when the stream is closed */ } }
  • 99. Firebase para Flow :) fun loadBooks(): Flow<List<Book>> = callbackFlow { val subscription = db.collection("books") .addSnapshotListener { snapshot, e -> if (e != null) { channel.close(e) } else { channel.offer(snapshotToList(it)) } } awaitClose { subscription.remove() } }
  • 100. Room
  • 101. Room dependencies { def room_version = "2.2.5" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" // 👇 Kotlin Extensions and Coroutines support for Room implementation "androidx.room:room-ktx:$room_version" ... }
  • 102. @Dao interface BookDao { @Query("SELECT * FROM book") suspend fun getAll(): List<Book> @Query("SELECT * FROM book WHERE id = :id") suspend fun getBook(id: Long): Book @Insert suspend fun insert(book: Book): Long @Delete suspend fun delete(book: Book) }
  • 103. @Dao interface BookDao { @Query("SELECT * FROM book") fun getAll(): Flow<List<Book>> @Query("SELECT * FROM book WHERE id = :id") fun getBook(id: Long): Flow<Book?> @Insert suspend fun insert(book: Book): Long @Delete suspend fun delete(book: Book) }
  • 104. launch { withContext(Dispatchers.IO) { val id = dao.insert( Book(0, "Dominando o Android", "Nelson Glauber") ) } // Do UI stuff }
  • 105. launch { // Room will run it in the specific context val id = dao.insert( Book(0, "Dominando o Android", "Nelson Glauber") ) // Do UI stuff }
  • 106. launch { dao.getAll().collect { bookList -> lstBooks.adapter = BookAdapter(context, bookList) } }
  • 107. class BookListViewModel( repository: BookRepository ): ViewModel() { val allBooks = repository.getAll().asLiveData() } Flow
  • 108. class BookListViewModel( repository: BookRepository ): ViewModel() { val allBooks = liveData { repository.getAll().collect { list -> emit(convertEntityList(list)) } } }
  • 109. Testes
  • 110. Testes de Coroutines testImplementation "junit:junit:4.12" testImplementation "androidx.arch.core:core-testing:2.0.0" testImplementation "io.mockk:mockk:1.9.3" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2"
  • 111. import kotlinx.coroutines.runBlocking import org.junit.* class CalculatorUnitTest { @Test fun sum_isCorrect() = runBlocking { val calc = Calculator() assertEquals(4, calc.sum(2, 2)) } }
  • 112. TestRule class TestCoroutineRule : TestRule { private val testCoroutineDispatcher = TestCoroutineDispatcher() private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) override fun apply(base: Statement, description: Description?) = object : Statement() { override fun evaluate() { Dispatchers.setMain(testCoroutineDispatcher) base.evaluate() Dispatchers.resetMain() // reset main dispatcher testCoroutineScope.cleanupTestCoroutines() } } fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = testCoroutineScope.runBlockingTest { block() } }
  • 113. TestRule class TestCoroutineRule : TestRule { private val testCoroutineDispatcher = TestCoroutineDispatcher() private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) override fun apply(base: Statement, description: Description?) = object : Statement() { override fun evaluate() { Dispatchers.setMain(testCoroutineDispatcher) base.evaluate() Dispatchers.resetMain() // reset main dispatcher testCoroutineScope.cleanupTestCoroutines() } } fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = testCoroutineScope.runBlockingTest { block() } }
  • 114. TestRule class TestCoroutineRule : TestRule { private val testCoroutineDispatcher = TestCoroutineDispatcher() private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) override fun apply(base: Statement, description: Description?) = object : Statement() { override fun evaluate() { Dispatchers.setMain(testCoroutineDispatcher) base.evaluate() Dispatchers.resetMain() // reset main dispatcher testCoroutineScope.cleanupTestCoroutines() } } fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = testCoroutineScope.runBlockingTest { block() } }
  • 115. TestRule class TestCoroutineRule : TestRule { private val testCoroutineDispatcher = TestCoroutineDispatcher() private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) override fun apply(base: Statement, description: Description?) = object : Statement() { override fun evaluate() { Dispatchers.setMain(testCoroutineDispatcher) base.evaluate() Dispatchers.resetMain() // reset main dispatcher testCoroutineScope.cleanupTestCoroutines() } } fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = testCoroutineScope.runBlockingTest { block() } }
  • 116. TestRule class TestCoroutineRule : TestRule { private val testCoroutineDispatcher = TestCoroutineDispatcher() private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) override fun apply(base: Statement, description: Description?) = object : Statement() { override fun evaluate() { Dispatchers.setMain(testCoroutineDispatcher) base.evaluate() Dispatchers.resetMain() // reset main dispatcher testCoroutineScope.cleanupTestCoroutines() } } fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = testCoroutineScope.runBlockingTest { block() } }
  • 117. Test class class ListBooksViewModelTest { @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @get:Rule val testCoroutineRule = TestCoroutineRule()
  • 118. Test @Test fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest { val books = DataFactory.dummyBookList() initMocks(books) bookListViewModel.loadBooks() coVerify(exactly = 1) { listBooksUseCase.execute() } val currentState = bookListViewModel.state().value assertEquals( ViewState.Status.SUCCESS, currentState?.status) assertEquals( books.map { BookConverter.fromData(it) }, currentState?.data) }
  • 119. Test @Test fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest { val books = DataFactory.dummyBookList() initMocks(books) bookListViewModel.loadBooks() coVerify(exactly = 1) { listBooksUseCase.execute() } val currentState = bookListViewModel.state().value assertEquals( ViewState.Status.SUCCESS, currentState?.status) assertEquals( books.map { BookConverter.fromData(it) }, currentState?.data) }
  • 120. Test @Test fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest { val books = DataFactory.dummyBookList() initMocks(books) bookListViewModel.loadBooks() coVerify(exactly = 1) { listBooksUseCase.execute() } val currentState = bookListViewModel.state().value assertEquals( ViewState.Status.SUCCESS, currentState?.status) assertEquals( books.map { BookConverter.fromData(it) }, currentState?.data) }
  • 121. Test @Test fun fetchBookReturnsSuccess() = testCoroutineRule.runBlockingTest { val books = DataFactory.dummyBookList() initMocks(books) bookListViewModel.loadBooks() coVerify(exactly = 1) { listBooksUseCase.execute() } val currentState = bookListViewModel.state().value assertEquals( ViewState.Status.SUCCESS, currentState?.status) assertEquals( books.map { BookConverter.fromData(it) }, currentState?.data) }
  • 122. • Coroutines vêm se tornando a forma de padrão para realizar código assíncrono no Android. • Essa é uma recomendação do Google. • Além do Jetpack, outras bibliotecas estão migrando (ou já migraram) pra Coroutines (ex: Retrofit, Apollo, MockK, …). Conclusão
  • 123. • Android Suspenders (Android Dev Summit 2018)
 https://www.youtube.com/watch?v=EOjq4OIWKqM • Understand Kotlin Coroutines on Android (Google I/O 2019)
 https://www.youtube.com/watch?v=BOHK_w09pVA • Coroutines Guide
 https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines- guide.md • Android Suspenders by Chris Banes (KotlinConf 2018)
 https://www.youtube.com/watch?v=P7ov_r1JZ1g • Room & Coroutines (Florina Muntenescu)
 https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5 Referências #1
  • 124. • Using Kotlin Coroutines in your Android App
 https://codelabs.developers.google.com/codelabs/kotlin-coroutines • Use Kotlin coroutines with Architecture Components
 https://developer.android.com/topic/libraries/architecture/coroutines • Create a Clean-Code App with Kotlin Coroutines and Android Architecture Components
 https://blog.elpassion.com/create-a-clean-code-app-with-kotlin-coroutines-and- android-architecture-components-f533b04b5431 • Android Coroutine Recipes (Dmytro Danylyk)
 https://proandroiddev.com/android-coroutine-recipes-33467a4302e9 • Kotlin Coroutines patterns & anti-patterns
 https://proandroiddev.com/kotlin-coroutines-patterns-anti-patterns-f9d12984c68e Referências #2
  • 125. • The reason to avoid GlobalScope (Roman Elizarov)
 https://medium.com/@elizarov/the-reason-to-avoid-globalscope-835337445abc • WorkManager meets Kotlin (Pietro Maggi)
 https://medium.com/androiddevelopers/workmanager-meets-kotlin-b9ad02f7405e • Coroutine Context and Scope (Roman Elizarov)
 https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055 • Easy Coroutines in Android: viewModelScope (Manuel Vivo)
 https://medium.com/androiddevelopers/easy-coroutines-in-android- viewmodelscope-25bffb605471 • Exceed the Android Speed Limit
 https://medium.com/androiddevelopers/exceed-the-android-speed-limit- b73a0692abc1 Referências #3
  • 126. • An Early look at Kotlin Coroutine’s Flow
 https://proandroiddev.com/an-early-look-at-kotlin-coroutines- flow-62e46baa6eb0 • Coroutines on Android (Sean McQuillan)
 https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the- background-3e0e54d20bb • Kotlin Flows and Coroutines (Roman Elizarov)
 https://medium.com/@elizarov/kotlin-flows-and-coroutines-256260fb3bdb • Simple design of Kotlin Flow (Roman Elizarov)
 https://medium.com/@elizarov/simple-design-of-kotlin-flow-4725e7398c4c • React Streams and Kotlin Flows (Roman Elizarov)
 https://medium.com/@elizarov/reactive-streams-and-kotlin-flows-bfd12772cda4 Referências #4
  • 127. • KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina Muntenescu & Manuel Vivo
 https://www.youtube.com/watch?v=w0kfnydnFWI • LiveData with Coroutines and Flow (Android Dev Summit '19)
 https://www.youtube.com/watch?v=B8ppnjGPAGE • Testing Coroutines on Android (Android Dev Summit '19)
 https://www.youtube.com/watch?v=KMb0Fs8rCRs Referências #5