Week 8 

Coroutines + Wallpaper App Setup continues

Goals: 

1. HW Discussion + Wallpaper App Setup

2. Coroutines Introdocution - Async

3. Homework for Weather App (Interview Style) 

Story time:

https://www.youtube.com/watch?v=YsMYSRw3BPU

Tutorial Hell

What is Async programming?

2 people wanting to dringk coffee but only one can have it without straw

in other words

1. don't stop my app when one process is running

2. don't return result immediately but later 

e.g. network requests, saving in db 

Concurrent Programming

Async Programming ->

Parallel Processing

 

Use your CPU in a way that you can achieve more than one process at one point of time

3 Types ->

1. UI / Main Thread : UI Stuff 

setting wallpaper / toast / add a new button

Dispatchers.Default

2. I/O -> Input Output Process

Retrofit (DOWNLOADING, UPLODAING DATA)

Dispatchers.IO

3. Default 

Dispatcher.main

not sure if you should optimize 

Previous implementations

Call<User> call = apiService.getUser(1);
call.enqueue(new Callback<User>() {
    @Override
    public void onResponse(Call<User> call, Response<User> response) {
        if (response.isSuccessful()) {
            User user = response.body();
            // Process the user data
        } else {
            // Handle error
        }
    }

    @Override
    public void onFailure(Call<User> call, Throwable t) {
        // Handle network failure
    }
});

Example with 2 req

// First network call
Call<User> call1 = apiService.getUser(1);
call1.enqueue(new Callback<User>() {
    @Override
    public void onResponse(Call<User> call, Response<User> response) {
        if (response.isSuccessful()) {
            User user = response.body();
            // Process the first user data

            // Second network call based on the result of the first call
            Call<SomeOtherData> call2 = apiService.getSomeOtherData(user.getId());
            call2.enqueue(new Callback<SomeOtherData>() {
                @Override
                public void onResponse(Call<SomeOtherData> call, Response<SomeOtherData> response) {
                    if (response.isSuccessful()) {
                        SomeOtherData otherData = response.body();
                        // Process the second data
                    } else {
                        // Handle error for the second call
                    }
                }

                @Override
                public void onFailure(Call<SomeOtherData> call, Throwable t) {
                    // Handle network failure for the second call
                }
            });
        } else {
            // Handle error for the first call
        }
    }

    @Override
    public void onFailure(Call<User> call, Throwable t) {
        // Handle network failure for the first call
    }
});

Problems with this approach

1. Complicated and hard to read

2. you have to handle in clear to clear in garbage collector

fun onCleared() { // in viewmodel
	call?.cancel()
}

RxJava Implementation

apiService.getUser(1)
    .flatMap(user -> apiService.getSomeOtherData(user.getId()))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        someOtherData -> {
            // Process the second data
        },
        throwable -> {
            // Handle network failure for the second call
        }
    );

Now Corooutine Implementation

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

val apiService = retrofit.create(ApiService::class.java)

viewModelScope.launch(Dispatchers.IO) { // IO thread if you don't pass then it's main thread
    try {
        val user = apiService.getUser(1)
        // Process the user data

        val someOtherData = apiService.getSomeOtherData(user.id)
        // Process the second data

    } catch (e: Exception) {
        // Handle network failure for either call
    }
}

// suspend is non blocking but delays the result only

Thread.sleep vs delay (thread below)

package com.example.wallpaperempty.data.api.model
fun main() {
    val mainStartTime = System.currentTimeMillis()
    println("Starting the task... (Time: $mainStartTime ms)")

    val startTime = System.currentTimeMillis()
    // Simulate a background task with Thread.sleep
    Thread {
        Thread.sleep(3000) // Sleep for 3 seconds
        val endTime = System.currentTimeMillis()
        val elapsedTime = endTime - startTime
        println("Task completed after 3 seconds. Elapsed time: $elapsedTime milliseconds")
    }.start()

    println("Task initiated, but main thread is blocked...")

    val mainEndTime = System.currentTimeMillis()
    val mainElapsedTime = mainEndTime - mainStartTime

    val mainThreadResumeStartTime = System.currentTimeMillis()
    // Main thread is blocked for 5 seconds
    Thread.sleep(5000) // Sleep for 5 seconds
    val mainThreadResumeEndTime = System.currentTimeMillis()
    val mainThreadResumeElapsedTime = mainThreadResumeEndTime - mainThreadResumeStartTime

    println("Main thread resumed. Elapsed time in the main thread: $mainElapsedTime milliseconds")
    println("Main thread resume elapsed time: $mainThreadResumeElapsedTime milliseconds")
}

Thread.sleep vs delay (delay below)

import kotlinx.coroutines.*

// Function to simulate a background task with delay
suspend fun simulateBackgroundTask() {
    val startTime = System.currentTimeMillis()
    delay(3000) // Delay for 3 seconds (3000 milliseconds)
    val endTime = System.currentTimeMillis()
    val elapsedTime = endTime - startTime
    println("Task completed after 3 seconds. Elapsed time: $elapsedTime milliseconds")
}

fun main() = runBlocking {
    val mainStartTime = System.currentTimeMillis()
    println("Starting the task... (Time: $mainStartTime ms)")

    // Launch a coroutine to perform the background task
    launch {
        simulateBackgroundTask()
    }

    println("Task initiated, but not blocked...${System.currentTimeMillis()}")

    val backgroundTaskStartTime = System.currentTimeMillis()
    // Give some time for the background task to complete
    delay(5000) // Delay for 5 seconds (5000 milliseconds)
    val backgroundTaskEndTime = System.currentTimeMillis()
    val backgroundTaskElapsedTime = backgroundTaskEndTime - backgroundTaskStartTime

    val mainEndTime = System.currentTimeMillis()
    val mainElapsedTime = mainEndTime - mainStartTime

    println("Main thread resumed. Elapsed time in the main thread: $mainElapsedTime milliseconds")
    println("Background task elapsed time: $backgroundTaskElapsedTime milliseconds")
}

Result: Lukas Lechner

Suspend functions

suspend fun performTask(): String {
    delay(2000) // Simulate a 2-second delay (non-blocking)
    return "Task Completed"
}

fun main() {
    println("Starting the task...")

    val job = GlobalScope.launch {
        val result = performTask()
        println(result)
    }

    // While the background task is running, the main thread can do other work
    println("Main thread is not blocked...")
}

Delay(3000) -> print() -> not printing

print("Hello")

print("After dely not blocking")

3 Types of Coroutine Builders

Coroutine Builder Purpose Result Type Use Case Example
launch Start a background coroutine without waiting for it Job Fire-and-forget tasks, parallel execution val job = GlobalScope.launch {
    // Do some background work
}
async Start a background coroutine and await a result Deferred<T> Concurrent tasks with results, parallelism val deferred = GlobalScope.async {
    // Do some background work and return a result
    "Result"
}
val result = deferred.await()
 
runBlocking Block the main thread temporarily N/A Main functions, testing, avoid in main thread
runBlocking {
    // Code that runs in a blocking manner
}

Global scope. launch


    val job = GlobalScope.launch {
        delay(500)
        println("printed in coroouint")
        // Network Call #1
        //user =  getUser()
        // Network Call #2
        // getamazonorders(user)
        
    }
    
    println("main ends")
    
    // nothing is printed unless thread.sleep

Why?

runBlocking

fun main() {
    println("Start")

    runBlocking {
        println("Before delay.")
        delay(1000)
        println("After delay.")
    }

    println("End")
}

async

fun main() {
    log("Start")

    runBlocking {
        log("Start of runBlocking")
        val number = async {
            log("Before delay.")
            delay(1000)
            log("After delay.")
            (1..100).random()
        }
        log("Continue with runBlocking...")
        log("End of runBlocking with value ${number.await()}")
    }

    log("End")
}

RealWorld Example

package com.example.wallpaperempty.presentation.adapter

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun simpleFlow(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(1000) // Simulate some asynchronous operation
        emit(i) // Emit values one by one
    }
}

fun main() {
    test()
    Thread.sleep(6000)
}


fun test() {
    GlobalScope.launch {
        val flow = simpleFlow()

        println("Start collecting flow")

        flow.collect { value ->
            println("Received: $value")
        }

        println("Flow collection completed")
    }
}

Rules

1. Suspend Function Should not block the caller thread

Documentation 

https://medium.com/kotlin-en-android/coroutines-con-kotlin-introducci%C3%B3n-a68f5eeee6a8

https://developer.android.com/kotlin/coroutines

Homework

1. Weather App: with API key moved out

2. Build UI for the App while handling weather of a location

3. Be creative : Store images or use a Text to Image API 

Week 8 why we need coroutines?

By Harnoor Singh

Week 8 why we need coroutines?

  • 245