├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── manuelvicnt │ │ └── mathcoroutines │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── manuelvicnt │ │ │ └── mathcoroutines │ │ │ ├── fibonacci │ │ │ └── FibonacciProducer.kt │ │ │ ├── main │ │ │ ├── MainActivity.kt │ │ │ ├── MainUserAction.kt │ │ │ ├── MainViewModel.kt │ │ │ └── MainViewState.kt │ │ │ └── number │ │ │ └── NumbersAPIService.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── manuelvicnt │ └── mathcoroutines │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MathCoroutines 2 | 3 | It uses Coroutines to calculate the Nth Fibonacci number and tells you a fun fact about that number N! 4 | 5 | You can either choose to get the fun fact or not. 6 | If you want it, we'll display the result only when the calculation finishes and we get a successful network request (when getting the fun fact!). 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | kotlin { 6 | experimental { 7 | coroutines 'enable' 8 | } 9 | } 10 | 11 | ext { 12 | kotlin_coroutines_version = '0.22.1' 13 | } 14 | 15 | android { 16 | compileSdkVersion 26 17 | defaultConfig { 18 | applicationId "com.manuelvicnt.mathcoroutines" 19 | minSdkVersion 16 20 | targetSdkVersion 26 21 | versionCode 1 22 | versionName "1.0" 23 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 35 | implementation 'com.android.support:appcompat-v7:26.1.0' 36 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 37 | 38 | // Kotlin coroutines 39 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" 40 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" 41 | 42 | // Architecture Components 43 | implementation "android.arch.lifecycle:extensions:1.1.0" 44 | annotationProcessor "android.arch.lifecycle:compiler:1.1.0" 45 | 46 | // Retrofit 47 | implementation 'com.squareup.retrofit2:retrofit:2.0.2' 48 | implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-experimental-adapter:1.0.0' 49 | 50 | testImplementation 'junit:junit:4.12' 51 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 52 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 53 | } 54 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/manuelvicnt/mathcoroutines/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.manuelvicnt.mathcoroutines", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/manuelvicnt/mathcoroutines/fibonacci/FibonacciProducer.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines.fibonacci 2 | 3 | object FibonacciProducer { 4 | 5 | fun fib(number: Long): Long = 6 | if (number <= 1) { 7 | number 8 | } else { 9 | fib(number - 1) + fib(number - 2) 10 | } 11 | 12 | 13 | // DON'T DO THIS. IT'S NOT WORTH IT. MISUSE OF COROUTINES 14 | // 15 | // private val fibonacciThreadPool = newFixedThreadPoolContext(2, "FibonacciThreadPool") 16 | // 17 | // suspend fun fib(context: CoroutineContext, number: Long): Long = 18 | // if (number <= 1) { 19 | // number 20 | // } else { 21 | // async(context + fibonacciThreadPool) { 22 | // fib(context, number - 1) 23 | // }.await() + 24 | // async(context + fibonacciThreadPool) { 25 | // fib(context, number - 2) 26 | // }.await() 27 | // } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/manuelvicnt/mathcoroutines/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines.main 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.view.View 7 | import com.manuelvicnt.mathcoroutines.R 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | import kotlinx.coroutines.experimental.* 10 | import kotlinx.coroutines.experimental.android.UI 11 | import kotlinx.coroutines.experimental.channels.consumeEach 12 | 13 | class MainActivity : AppCompatActivity() { 14 | 15 | private lateinit var viewModel: MainViewModel 16 | private val parentJob = Job() 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_main) 21 | viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) 22 | 23 | setupViews() 24 | } 25 | 26 | override fun onStart() { 27 | super.onStart() 28 | listenViewModel() 29 | } 30 | 31 | override fun onStop() { 32 | parentJob.cancel() 33 | super.onStop() 34 | } 35 | 36 | private fun setupViews() { 37 | calcButton.setOnClickListener { 38 | viewModel.userActionActor.offer(MainUserAction.Calculate(input.text.toString().toLong())) 39 | } 40 | 41 | funFact.setOnCheckedChangeListener { _, isChecked -> 42 | viewModel.userActionActor.offer(MainUserAction.FunFactEnabled(isChecked)) 43 | } 44 | } 45 | 46 | private fun listenViewModel() { 47 | // Launch on the CommonPool to not block the MainThread 48 | launch(parentJob + CommonPool) { 49 | viewModel.viewStateChannel.consumeEach { 50 | withContext(UI) { 51 | when (it) { 52 | MainViewState.Loading -> { 53 | progressBar.visibility = View.VISIBLE 54 | result.text = "Loading..." 55 | funFactText.text = "" 56 | } 57 | is MainViewState.Rendered -> { 58 | progressBar.visibility = View.GONE 59 | result.text = "Fibonacci = ${it.fibonacciNumber.toInt()}" 60 | funFactText.text = "${it.funFact}" 61 | } 62 | MainViewState.WrongInputError -> { 63 | showError() 64 | } 65 | MainViewState.RequestError -> { 66 | showError() 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | private fun showError() { 75 | progressBar.visibility = View.GONE 76 | result.text = "Something happened when calculating your input! Sorry!" 77 | funFactText.text = "" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/manuelvicnt/mathcoroutines/main/MainUserAction.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines.main 2 | 3 | sealed class MainUserAction { 4 | class Calculate(val number: Long) : MainUserAction() 5 | class FunFactEnabled(val enabled: Boolean) : MainUserAction() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/manuelvicnt/mathcoroutines/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines.main 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.util.Log 5 | import com.manuelvicnt.mathcoroutines.fibonacci.FibonacciProducer 6 | import com.manuelvicnt.mathcoroutines.number.NumbersApiHelper 7 | import com.manuelvicnt.mathcoroutines.number.NumbersApiService 8 | import kotlinx.coroutines.experimental.* 9 | import kotlinx.coroutines.experimental.channels.ActorScope 10 | import kotlinx.coroutines.experimental.channels.ConflatedBroadcastChannel 11 | import kotlinx.coroutines.experimental.channels.actor 12 | 13 | class MainViewModel : ViewModel() { 14 | 15 | private val parentJob = Job() 16 | private val numbersApiService = NumbersApiService(NumbersApiHelper.numbersApi) 17 | private var askForFunFact = false 18 | 19 | // Can be replaced by LiveData 20 | val viewStateChannel = ConflatedBroadcastChannel() 21 | 22 | val userActionActor = actor(CommonPool, parent = parentJob) { 23 | for (msg in channel) { // iterate over incoming messages 24 | when (msg) { 25 | is MainUserAction.Calculate -> { 26 | if (msg.number <= 0) { 27 | viewStateChannel.offer(MainViewState.WrongInputError) 28 | } else { 29 | viewStateChannel.offer(MainViewState.Loading) 30 | processCalculation(msg) 31 | } 32 | } 33 | is MainUserAction.FunFactEnabled -> { 34 | askForFunFact = msg.enabled 35 | } 36 | } 37 | } 38 | } 39 | 40 | override fun onCleared() { 41 | viewStateChannel.close() 42 | parentJob.cancel() 43 | super.onCleared() 44 | } 45 | 46 | private suspend fun ActorScope.processCalculation(msg: MainUserAction.Calculate) { 47 | val fibonacciResult: Long 48 | var funFactResult: String? = null 49 | 50 | // Make Async Requests 51 | val fibonacciDeferred = async(parentJob + coroutineContext) { 52 | FibonacciProducer.fib(msg.number) 53 | } 54 | var funFactDeferred: Deferred? = null 55 | if (askForFunFact) { 56 | funFactDeferred = async(parentJob + coroutineContext) { 57 | numbersApiService.getNumberFunFact(coroutineContext, msg.number) 58 | } 59 | } 60 | 61 | // Wait for both results to come 62 | fibonacciResult = fibonacciDeferred.await() 63 | if (askForFunFact) { 64 | funFactDeferred?.let { 65 | funFactResult = it.await() 66 | } 67 | } 68 | 69 | // Process response 70 | if (askForFunFact && funFactResult != null && funFactResult == "") { 71 | viewStateChannel.offer(MainViewState.RequestError) 72 | } else { 73 | viewStateChannel.offer(MainViewState.Rendered(fibonacciResult, funFactResult ?: "")) 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/manuelvicnt/mathcoroutines/main/MainViewState.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines.main 2 | 3 | sealed class MainViewState { 4 | object Loading : MainViewState() 5 | class Rendered(val fibonacciNumber: Long, val funFact: String) : MainViewState() 6 | object WrongInputError : MainViewState() 7 | object RequestError : MainViewState() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/manuelvicnt/mathcoroutines/number/NumbersAPIService.kt: -------------------------------------------------------------------------------- 1 | package com.manuelvicnt.mathcoroutines.number 2 | 3 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.experimental.CoroutineCallAdapterFactory 4 | import kotlinx.coroutines.experimental.CommonPool 5 | import kotlinx.coroutines.experimental.Deferred 6 | import kotlinx.coroutines.experimental.launch 7 | import okhttp3.ResponseBody 8 | import retrofit2.Response 9 | import retrofit2.Retrofit 10 | import retrofit2.http.GET 11 | import retrofit2.http.Path 12 | import kotlin.coroutines.experimental.CoroutineContext 13 | 14 | class NumbersApiService(private val numbersApi: NumbersApiInterface) { 15 | 16 | suspend fun getNumberFunFact(context: CoroutineContext, number: Long): String { 17 | var result = "" 18 | 19 | launch(context + CommonPool) { 20 | result = getFunFactResponse(number) 21 | }.join() 22 | 23 | return result 24 | } 25 | 26 | private suspend fun getFunFactResponse(number: Long): String = 27 | try { 28 | val response = numbersApi.getFunFact(number).await() 29 | if (response.isSuccessful) { 30 | response.body()?.string() ?: "" 31 | } else { 32 | "" 33 | } 34 | } catch (e: Exception) { 35 | "" 36 | } 37 | } 38 | 39 | object NumbersApiHelper { 40 | 41 | private val retrofit = Retrofit.Builder() 42 | .baseUrl("http://numbersapi.com") 43 | .addCallAdapterFactory(CoroutineCallAdapterFactory()) 44 | .build() 45 | 46 | val numbersApi: NumbersApiInterface 47 | get() = retrofit.create(NumbersApiInterface::class.java) 48 | } 49 | 50 | interface NumbersApiInterface { 51 | 52 | @GET("/{number}") 53 | fun getFunFact(@Path("number") number: Long): Deferred> 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 |