├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── br │ │ └── com │ │ └── wellingtoncosta │ │ └── coroutines │ │ ├── app │ │ ├── App.kt │ │ ├── config │ │ │ ├── koinModules.kt │ │ │ └── retrofitConfig.kt │ │ └── ui │ │ │ ├── ListUsersActivity.kt │ │ │ ├── ListUsersAdapter.kt │ │ │ └── ListUsersViewModel.kt │ │ ├── domain │ │ ├── model │ │ │ └── User.kt │ │ └── repository │ │ │ └── UserRepository.kt │ │ └── resources │ │ ├── remote │ │ ├── api │ │ │ └── GithubApi.kt │ │ └── response │ │ │ └── UserResponse.kt │ │ └── repository │ │ └── UserDataRepository.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_list_users.xml │ └── list_users_item.xml │ ├── menu │ └── search_menu.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 ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | 5 | # IDEA config files 6 | .idea/ 7 | /.idea/workspace.xml 8 | /.idea/libraries 9 | /.idea/misc.xml 10 | .idea/modules.xml 11 | /.idea/vcs.xml 12 | 13 | # Java class files 14 | *.class 15 | 16 | # Legacy Eclipse project files 17 | .classpath 18 | .project 19 | 20 | ### Eclipse ### 21 | 22 | .metadata 23 | tmp/ 24 | *.tmp 25 | *.bak 26 | *~.nib 27 | .settings/ 28 | .loadpath 29 | .recommenders 30 | 31 | 32 | 33 | /build 34 | /captures 35 | .externalNativeBuild -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Wellington Costa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-kotlin-coroutines 2 | 3 | A simple Android project using asynchronous programming with Kotlin Coroutines. 4 | 5 | This project uses the following stack: 6 | 7 | - Android Jetpack Live Data 8 | - Android Jetpack View Model 9 | - Fresco 10 | - Google Material Design for Android 11 | - Koin 12 | - Kotlin 13 | - Kotlin Coroutines 14 | - Moshi 15 | - Retrofit -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | kotlin("kapt") 5 | } 6 | 7 | android { 8 | compileSdkVersion(28) 9 | 10 | defaultConfig { 11 | applicationId = "br.com.wellingtoncosta.coroutines" 12 | minSdkVersion(16) 13 | targetSdkVersion(28) 14 | versionCode = 1 15 | versionName = "1.0" 16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | getByName("release") { 21 | isMinifyEnabled = false 22 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 23 | } 24 | } 25 | 26 | dataBinding { 27 | isEnabled = true 28 | } 29 | 30 | aaptOptions { 31 | cruncherEnabled = false 32 | useNewCruncher = false 33 | } 34 | } 35 | 36 | dependencies { 37 | //AndroidX 38 | implementation("androidx.appcompat:appcompat:1.0.2") 39 | implementation("androidx.constraintlayout:constraintlayout:1.1.3") 40 | implementation("androidx.core:core-ktx:1.0.2") 41 | 42 | // AndroidX Data Binding 43 | kapt("androidx.databinding:databinding-compiler:3.4.1") 44 | 45 | // AndroidX LiveData and ViewModel 46 | implementation("androidx.lifecycle:lifecycle-extensions:2.0.0") 47 | 48 | // Fresco 49 | implementation("com.facebook.fresco:fresco:2.0.0") 50 | 51 | // Google Material 52 | implementation("com.google.android.material:material:1.0.0") 53 | 54 | // Kotlin 55 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.40") 56 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2") 57 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2") 58 | 59 | // Koin 60 | implementation("org.koin:koin-androidx-scope:1.0.2") 61 | implementation("org.koin:koin-androidx-viewmodel:1.0.2") 62 | 63 | // Retrofit 64 | implementation("com.squareup.retrofit2:retrofit:2.4.0") 65 | implementation("com.squareup.retrofit2:converter-moshi:2.4.0") 66 | implementation("com.squareup.moshi:moshi-kotlin:1.8.0") 67 | implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2") 68 | implementation("com.squareup.okhttp3:logging-interceptor:3.11.0") 69 | 70 | // Material Search View 71 | implementation("com.miguelcatalan:materialsearchview:1.4.0") 72 | 73 | // Tests 74 | testImplementation("junit:junit:4.12") 75 | androidTestImplementation("androidx.test:runner:1.2.0") 76 | androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0") 77 | } -------------------------------------------------------------------------------- /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.old. 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/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/app/App.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.app 2 | 3 | import android.app.Application 4 | import br.com.wellingtoncosta.coroutines.app.config.remoteModule 5 | import br.com.wellingtoncosta.coroutines.app.config.repositoryModule 6 | import br.com.wellingtoncosta.coroutines.app.config.uiModule 7 | import com.facebook.drawee.backends.pipeline.Fresco 8 | import org.koin.android.ext.android.startKoin 9 | 10 | /** 11 | * @author Wellington Costa on 22/04/18. 12 | */ 13 | open class App : Application() { 14 | 15 | private val appModules by lazy { 16 | listOf(remoteModule, repositoryModule, uiModule) 17 | } 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | startKoin(this, appModules) 23 | 24 | Fresco.initialize(this) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/app/config/koinModules.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.app.config 2 | 3 | import br.com.wellingtoncosta.coroutines.resources.repository.UserDataRepository 4 | import br.com.wellingtoncosta.coroutines.resources.remote.api.GithubApi 5 | import br.com.wellingtoncosta.coroutines.domain.repository.UserRepository 6 | import br.com.wellingtoncosta.coroutines.app.ui.ListUsersViewModel 7 | import org.koin.androidx.viewmodel.ext.koin.viewModel 8 | import org.koin.dsl.module.module 9 | 10 | val uiModule = module { 11 | viewModel { ListUsersViewModel(get()) } 12 | } 13 | 14 | val repositoryModule = module { 15 | single { UserDataRepository(get()) } 16 | } 17 | 18 | val remoteModule = module { 19 | single { provideOkHttpClient() } 20 | single { provideRetrofit(get()) } 21 | single { createApi(get()) } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/app/config/retrofitConfig.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.app.config 2 | 3 | import br.com.wellingtoncosta.coroutines.BuildConfig 4 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory 5 | import com.squareup.moshi.Moshi 6 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 7 | import okhttp3.OkHttpClient 8 | import okhttp3.logging.HttpLoggingInterceptor 9 | import retrofit2.Retrofit 10 | import retrofit2.converter.moshi.MoshiConverterFactory 11 | import java.util.concurrent.TimeUnit 12 | 13 | const val API_URL = "https://api.github.com" 14 | 15 | const val CONNECTION_TIMEOUT = 60000L 16 | 17 | internal fun provideOkHttpClient(): OkHttpClient { 18 | val client = OkHttpClient.Builder() 19 | .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) 20 | .readTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) 21 | .writeTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) 22 | 23 | if(BuildConfig.DEBUG) { 24 | val logging = HttpLoggingInterceptor() 25 | logging.level = HttpLoggingInterceptor.Level.BODY 26 | client.addInterceptor(logging) 27 | } 28 | 29 | return client.build() 30 | } 31 | 32 | internal fun createMoshi() = Moshi.Builder() 33 | .add(KotlinJsonAdapterFactory()) 34 | .build() 35 | 36 | internal fun provideRetrofit(okHttpClient: OkHttpClient) = Retrofit.Builder() 37 | .baseUrl(API_URL) 38 | .addCallAdapterFactory(CoroutineCallAdapterFactory()) 39 | .addConverterFactory(MoshiConverterFactory.create(createMoshi())) 40 | .client(okHttpClient) 41 | .build() 42 | 43 | internal inline fun createApi(retrofit: Retrofit) = retrofit.create(T::class.java) 44 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/app/ui/ListUsersActivity.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.app.ui 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.Menu 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.databinding.DataBindingUtil 8 | import androidx.lifecycle.Observer 9 | import androidx.recyclerview.widget.DividerItemDecoration 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import br.com.wellingtoncosta.coroutines.R 12 | import com.miguelcatalan.materialsearchview.MaterialSearchView 13 | import org.koin.androidx.viewmodel.ext.android.viewModel 14 | import br.com.wellingtoncosta.coroutines.databinding.ActivityListUsersBinding as Binding 15 | 16 | /** 17 | * @author Wellington Costa on 22/04/18 18 | */ 19 | class ListUsersActivity : AppCompatActivity() { 20 | 21 | private val viewModel by viewModel() 22 | 23 | private val binding by lazy { 24 | DataBindingUtil.setContentView(this, R.layout.activity_list_users) 25 | } 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | binding.viewModel = viewModel 30 | binding.setLifecycleOwner(this) 31 | 32 | observeUsers() 33 | observeError() 34 | 35 | setupSearchView() 36 | setupRecyclerView() 37 | } 38 | 39 | override fun onResume() { 40 | super.onResume() 41 | viewModel.getAll() 42 | } 43 | 44 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 45 | menuInflater.inflate(R.menu.search_menu, menu) 46 | val item = menu.findItem(R.id.action_search) 47 | binding.searchView.setMenuItem(item) 48 | return true 49 | } 50 | 51 | override fun onBackPressed() { 52 | if (binding.searchView.isSearchOpen) { 53 | binding.searchView.closeSearch() 54 | } else { 55 | super.onBackPressed() 56 | } 57 | } 58 | 59 | private fun setupSearchView() { 60 | setSupportActionBar(binding.toolbar) 61 | binding.searchView.setOnSearchViewListener(object : MaterialSearchView.SearchViewListener { 62 | override fun onSearchViewShown() { } 63 | override fun onSearchViewClosed() { viewModel.getAll() } 64 | }) 65 | 66 | binding.searchView.setOnQueryTextListener(object : MaterialSearchView.OnQueryTextListener { 67 | override fun onQueryTextSubmit(query: String): Boolean { 68 | viewModel.getByUsername(query) 69 | return true 70 | } 71 | 72 | override fun onQueryTextChange(newText: String) = true 73 | }) 74 | } 75 | 76 | private fun setupRecyclerView() { 77 | val dividerItemDecoration = DividerItemDecoration( 78 | binding.recyclerView.context, 79 | (binding.recyclerView.layoutManager as LinearLayoutManager).orientation 80 | ) 81 | binding.recyclerView.addItemDecoration(dividerItemDecoration) 82 | } 83 | 84 | private fun observeUsers() { 85 | viewModel.users.observe(this, 86 | Observer { users -> binding.recyclerView.adapter = 87 | ListUsersAdapter(users) 88 | } 89 | ) 90 | } 91 | 92 | private fun observeError() { 93 | viewModel.error.observe( 94 | this, 95 | Observer { t -> 96 | Log.e("LOAD_USERS", "$t") 97 | }) 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/app/ui/ListUsersAdapter.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.app.ui 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import br.com.wellingtoncosta.coroutines.R 8 | import br.com.wellingtoncosta.coroutines.databinding.ListUsersItemBinding 9 | import br.com.wellingtoncosta.coroutines.domain.model.User 10 | import com.facebook.drawee.generic.RoundingParams 11 | 12 | /** 13 | * @author Wellington Costa on 23/04/18 14 | */ 15 | class ListUsersAdapter( 16 | private val users: List 17 | ) : RecyclerView.Adapter() { 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 20 | return ListUsersViewHolder( 21 | LayoutInflater 22 | .from(parent.context) 23 | .inflate(R.layout.list_users_item, parent, false) 24 | ) 25 | } 26 | 27 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 28 | val binding = (holder as ListUsersViewHolder).binding 29 | val user = users[position] 30 | loadImage(binding, user) 31 | binding.user = user 32 | } 33 | 34 | private fun loadImage(binding: ListUsersItemBinding, user: User) { 35 | val roundingParams = RoundingParams.fromCornersRadius(5f) 36 | roundingParams.roundAsCircle = true 37 | binding.image.hierarchy.roundingParams = roundingParams 38 | binding.image.setImageURI(user.avatarUrl) 39 | } 40 | 41 | override fun getItemCount() = users.size 42 | 43 | } 44 | 45 | class ListUsersViewHolder(view: View) : RecyclerView.ViewHolder(view) { 46 | 47 | val binding: ListUsersItemBinding = ListUsersItemBinding.bind(view) 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/app/ui/ListUsersViewModel.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.app.ui 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import br.com.wellingtoncosta.coroutines.domain.model.User 7 | import br.com.wellingtoncosta.coroutines.domain.repository.UserRepository 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers.Main 10 | import kotlinx.coroutines.SupervisorJob 11 | import kotlinx.coroutines.launch 12 | 13 | /** 14 | * @author Wellington Costa on 23/04/18 15 | */ 16 | class ListUsersViewModel( 17 | private val repository: UserRepository 18 | ) : ViewModel() { 19 | 20 | private val viewModelJob = SupervisorJob() 21 | 22 | private val viewModeScope = CoroutineScope(Main + viewModelJob) 23 | 24 | private val _users: MutableLiveData> = MutableLiveData() 25 | 26 | private val _loading: MutableLiveData = MutableLiveData() 27 | 28 | private val _error: MutableLiveData = MutableLiveData() 29 | 30 | val users: LiveData> get() = _users 31 | 32 | val loading: LiveData get() = _loading 33 | 34 | val error: LiveData get() = _error 35 | 36 | fun getAll() { 37 | viewModeScope.launch { 38 | _loading.value = true 39 | try { 40 | _users.value = repository.getAll() 41 | _loading.value = false 42 | } catch(t: Throwable) { 43 | _users.value = emptyList() 44 | _error.value = t 45 | } finally { 46 | _loading.value = false 47 | } 48 | } 49 | } 50 | 51 | fun getByUsername(username: String) { 52 | viewModeScope.launch { 53 | _loading.value = true 54 | try { 55 | val user = repository.getByUsername(username) 56 | _users.value = listOf(user) 57 | } catch(t: Throwable) { 58 | _users.value = emptyList() 59 | _error.value = t 60 | } 61 | finally { 62 | _loading.value = false 63 | } 64 | } 65 | } 66 | 67 | override fun onCleared() { 68 | super.onCleared() 69 | viewModelJob.cancel() 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/domain/model/User.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.domain.model 2 | 3 | /** 4 | * @author Wellington Costa on 22/04/18. 5 | */ 6 | data class User (val avatarUrl: String, val username: String) -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.domain.repository 2 | 3 | import br.com.wellingtoncosta.coroutines.domain.model.User 4 | import kotlinx.coroutines.Deferred 5 | 6 | /** 7 | * @author Wellington Costa on 23/04/18 8 | */ 9 | interface UserRepository { 10 | 11 | suspend fun getAll(): List 12 | 13 | suspend fun getByUsername(username: String): User 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/resources/remote/api/GithubApi.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.resources.remote.api 2 | 3 | import br.com.wellingtoncosta.coroutines.resources.remote.response.UserResponse 4 | import kotlinx.coroutines.Deferred 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | 8 | /** 9 | * @author Wellington Costa on 22/04/18. 10 | */ 11 | interface GithubApi { 12 | 13 | @GET("users") 14 | fun getAll(): Deferred> 15 | 16 | @GET("users/{username}") 17 | fun getByUsername(@Path("username") username: String): Deferred 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/resources/remote/response/UserResponse.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.resources.remote.response 2 | 3 | import br.com.wellingtoncosta.coroutines.domain.model.User 4 | import com.squareup.moshi.Json 5 | 6 | /** 7 | * @author Wellington Costa on 03/12/18 8 | */ 9 | data class UserResponse( 10 | @Json(name = "avatar_url") val avatarUrl: String, 11 | @Json(name = "login") val username: String 12 | ) 13 | 14 | fun UserResponse.toModel() = User(this.avatarUrl, this.username) -------------------------------------------------------------------------------- /app/src/main/java/br/com/wellingtoncosta/coroutines/resources/repository/UserDataRepository.kt: -------------------------------------------------------------------------------- 1 | package br.com.wellingtoncosta.coroutines.resources.repository 2 | 3 | import br.com.wellingtoncosta.coroutines.domain.repository.UserRepository 4 | import br.com.wellingtoncosta.coroutines.resources.remote.api.GithubApi 5 | import br.com.wellingtoncosta.coroutines.resources.remote.response.toModel 6 | import kotlinx.coroutines.Dispatchers.IO 7 | import kotlinx.coroutines.withContext 8 | 9 | /** 10 | * @author Wellington Costa on 23/04/18 11 | */ 12 | class UserDataRepository( 13 | private val api: GithubApi 14 | ) : UserRepository { 15 | 16 | override suspend fun getAll() = withContext(IO) { 17 | api.getAll().await().map { it.toModel() } 18 | } 19 | 20 | override suspend fun getByUsername(username: String) = withContext(IO) { 21 | api.getByUsername(username).await().toModel() 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /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_list_users.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 22 | 23 | 27 | 28 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_users_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 26 | 27 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/menu/search_menu.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wellingtoncosta/android-kotlin-coroutines/30f4919bde3f549a7380bade35b8bdb295e31c7d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #212121 8 | #757575 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android Kotlin Coroutines 3 | GitHub Users 4 | Search Users 5 | Could not connect to server 6 | Could not get the users 7 | User not found 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 |