├── .github └── FUNDING.yml ├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── markdown-exported-files.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── _config.yml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── androidachitecturecomponent │ │ └── example │ │ └── com │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── androidachitecturecomponent │ │ │ └── example │ │ │ ├── App.kt │ │ │ ├── data │ │ │ ├── local │ │ │ │ ├── LocalDataSource.kt │ │ │ │ ├── UserDao.kt │ │ │ │ └── UserEntity.kt │ │ │ ├── remote │ │ │ │ ├── RemoteDataSource.kt │ │ │ │ ├── RemoteService.kt │ │ │ │ └── UserResponse.kt │ │ │ └── repository │ │ │ │ ├── Repository.kt │ │ │ │ └── RepositoryDataSource.kt │ │ │ ├── di │ │ │ ├── AppComponent.kt │ │ │ └── AppModule.kt │ │ │ ├── ui │ │ │ ├── MainActivity.kt │ │ │ └── user │ │ │ │ ├── UserAdapter.kt │ │ │ │ ├── UserViewModel.kt │ │ │ │ └── UsersFragment.kt │ │ │ └── utils │ │ │ └── Transformation.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_users.xml │ │ └── item_view.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 │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── androidachitecturecomponent │ └── example │ └── com │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── media ├── architecture_component.png └── capture.png ├── settings.gradle └── versions.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: DouiriAli # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: alidouiri # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/markdown-exported-files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ali DOUIRI 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 | 2 | You can support me buy contributing code, filing bugs, asking/answering questions, or buying me a coffee. 3 | 4 | 5 | Buy Me a Coffee at ko-fi.com 6 | 7 | 8 | # Android Architecture Components with Kotlin 9 | 10 | In this example we're building a UI that shows a github users. 11 | 12 | ![](media/architecture_component.png) 13 | 14 | # Building the user interface 15 | 16 | The UI will consist of a fragment UsersFragment.kt and its corresponding layout file fragment_users.xml. 17 | 18 | We will create a UserViewModel.kt based on the ViewModel class to keep this information. 19 | 20 | ``` 21 | A ViewModel provides the data for a specific UI component, such as a fragment or activity, and handles the communication with the business part of data handling, such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an activity due to rotation. 22 | ``` 23 | 24 | Now we have 3 files. 25 | 26 | fragment_users.xml: The UI definition for the screen. 27 | 28 | UserViewModel.java: The class that prepares the data for the UI. 29 | 30 | UsersFragment.java: The UI controller that displays the data in the ViewModel and reacts to user interactions. 31 | 32 | Now, we have these three code modules, how do we connect them? After all, when the ViewModel's user field is set, we need a way to inform the UI. This is where the LiveData class comes in. 33 | 34 | ``` 35 | LiveData is an observable data holder. It lets the components in your app observe LiveData objects for changes without creating explicit and rigid dependency paths between them. LiveData also respects the lifecycle state of your app components (activities, fragments, services) and does the right thing to prevent object leaking so that your app does not consume more memory. 36 | ``` 37 | 38 | Every time the user data is updated, the onChanged callback will be invoked and the UI will be refreshed. 39 | 40 | If you are familiar with other libraries where observable callbacks are used, you might have realized that we didn't have to override the fragment's onStop() method to stop observing the data. This is not necessary with LiveData because it is lifecycle aware, which means it will not invoke the callback unless the fragment is in an active state (received onStart() but did not receive onStop()). LiveData will also automatically remove the observer when the fragment receives onDestroy(). 41 | 42 | We also didn't do anything special to handle configuration changes (for example, user rotating the screen). The ViewModel is automatically restored when the configuration changes, so as soon as the new fragment comes to life, it will receive the same instance of ViewModel and the callback will be called instantly with the current data. This is the reason why ViewModels should not reference Views directly; they can outlive the View's lifecycle. [See The lifecycle of a ViewModel][7]. 43 | 44 | # Fetching data 45 | 46 | Now we have connected the ViewModel to the fragment, but how does the ViewModel fetch the user data? In this example, we use a REST API of Github to retrieve the list of users. We will use the Retrofit library to access. 47 | 48 | Here's our retrofit Webservice : 49 | 50 | ```kotlin 51 | 52 | interface RemoteService { 53 | 54 | @GET("users") 55 | fun getUsersFromApi() : Observable>> 56 | } 57 | 58 | ``` 59 | 60 | A naive implementation of the ViewModel could directly call the Webservice to fetch the data and assign it back to the user object. Even though it works, your app will be difficult to maintain as it grows. It gives too much responsibility to the ViewModel class which goes against the principle of separation of concerns that we've mentioned earlier. Additionally, the scope of a ViewModel is tied to an Activity or Fragment lifecycle, so losing all of the data when its lifecycle is finished is a bad user experience. Instead, our ViewModel will delegate this work to a new Repository module. 61 | 62 | ``` 63 | Repository modules are responsible for handling data operations. They provide a clean API to the rest of the app. They know where to get the data from and what API calls to make when data is updated. You can consider them as mediators between different data sources (persistent model, web service, cache, etc.) 64 | ``` 65 | 66 | ```kotlin 67 | 68 | class RepositoryDataSource constructor(private val remoteDataSource: RemoteDataSource) : Repository { 69 | 70 | private val data = MutableLiveData>() 71 | 72 | override fun getUsersFromApi() : LiveData { 73 | 74 | remoteDataSource.getUsersFromApi() 75 | .subscribeOn(Schedulers.io()) 76 | .observeOn(AndroidSchedulers.mainThread()) 77 | .subscribe({ result -> data.setValue(Transformation.toUsersEtities(result.body()!!)) }, 78 | { error -> Log.e(TAG, "{$error.message}") }, 79 | { Log.d(TAG, "completed") }) 80 | 81 | return data; 82 | } 83 | 84 | ``` 85 | 86 | # Connecting ViewModel and the repository 87 | 88 | Now we modify our UserViewModel to use the repository. 89 | 90 | ```kotlin 91 | 92 | class UserViewModel : ViewModel() { 93 | 94 | @Inject 95 | lateinit var mRepositoryDataSource : RepositoryDataSource 96 | 97 | init { 98 | 99 | App.mAppComponent.inject(this) 100 | } 101 | 102 | fun getUsers() : LiveData> = mRepositoryDataSource.getUsersFromApi() 103 | 104 | } 105 | 106 | ``` 107 | 108 | # Persisting data 109 | 110 | With the current implementation, we will need to fetch the data again from the network. This is not only a bad user experience, but also wasteful since it will use mobile data to re-fetch the same data. You could simply fix this by caching the web requests, but it creates new problems. What happens if the same user data shows up from another type of request (e.g., fetching a list of friends)? Then your app will possibly show inconsistent data, which is a confusing user experience at best. For instance, the same user's data may show up differently because the list-of-friends request and user request could be executed at different times. Your app needs to merge them to avoid showing inconsistent data. 111 | 112 | The proper way to handle this is to use a persistent model. This is where the Room persistence library comes to the rescue. 113 | 114 | ``` 115 | Room is an object mapping library that provides local data persistence with minimal boilerplate code. At compile time, it validates each query against the schema, so that broken SQL queries result in compile time errors instead of runtime failures. Room abstracts away some of the underlying implementation details of working with raw SQL tables and queries. It also allows observing changes to the database data (including collections and join queries), exposing such changes via LiveData objects. In addition, it explicitly defines thread constraints that address common issues such as accessing storage on the main thread. 116 | ``` 117 | 118 | To use Room, we need to define our local schema. First, annotate the User class with @Entity to mark it as a table in your database. 119 | 120 | ```kotlin 121 | 122 | @Entity(tableName = "users") 123 | data class UserEntity( 124 | 125 | @PrimaryKey(autoGenerate = true) 126 | var id : Long = 0, 127 | var name : String, 128 | var avatar : String 129 | ) 130 | 131 | ``` 132 | 133 | Then, create a database class by extending RoomDatabase for your app: 134 | 135 | ```kotlin 136 | 137 | @Singleton 138 | @Database(entities = arrayOf(UserEntity::class), version = 1) 139 | abstract class LocalDataSource : RoomDatabase() {} 140 | 141 | ``` 142 | 143 | Notice that LocalDataSource is abstract. Room automatically provides an implementation of it. See the [Room][8] documentation for details. 144 | 145 | Now we need a way to get, insert and delete the users data into the database. For this, we'll create a data access object (DAO). 146 | 147 | ```kotlin 148 | 149 | @Dao 150 | interface UserDao { 151 | 152 | @Query("SELECT * FROM users") 153 | fun getUsers() : LiveData> 154 | 155 | @Insert(onConflict = OnConflictStrategy.REPLACE) 156 | fun saveUsers(users : List) 157 | 158 | @Query("DELETE FROM users") 159 | fun deleteUsers() 160 | } 161 | 162 | ``` 163 | 164 | Then, reference the DAO from our database class. 165 | 166 | 167 | ```kotlin 168 | 169 | @Singleton 170 | @Database(entities = arrayOf(UserEntity::class), version = 1) 171 | abstract class LocalDataSource : RoomDatabase() { 172 | 173 | abstract fun getUserDao() : UserDao 174 | 175 | } 176 | 177 | ``` 178 | 179 | Notice that the load method returns a LiveData. Room knows when the database is modified and it will automatically notify all active observers when the data changes. Because it is using LiveData, this will be efficient because it will update the data only if there is at least one active observer. 180 | 181 | Now we can modify our RepositoryDataSource to incorporate the Room data source. 182 | 183 | ```kotlin 184 | 185 | class RepositoryDataSource constructor(private val remoteDataSource: RemoteDataSource, 186 | private val roomDataSource: LocalDataSource) : Repository { 187 | 188 | /** 189 | * Get users from database 190 | */ 191 | override fun getUsersFromDb(): LiveData> = roomDataSource.getUserDao().getUsers() 192 | 193 | /** 194 | * Get users from api 195 | */ 196 | override fun getUsersFromApi() : LiveData { 197 | 198 | remoteDataSource.getUsersFromApi() 199 | .subscribeOn(Schedulers.io()) 200 | .observeOn(AndroidSchedulers.mainThread()) 201 | .subscribe({ result -> data.setValue(Transformation.toUsersEtities(result.body()!!)) }, 202 | { error -> Log.e(TAG, "{$error.message}") }, 203 | { Log.d(TAG, "completed") }) 204 | 205 | return data; 206 | } 207 | 208 | /** 209 | * Save users into database 210 | */ 211 | override fun saveUsers(users: List) { 212 | 213 | roomDataSource.getUserDao().saveUsers(users) 214 | } 215 | 216 | /** 217 | * Delete all users 218 | */ 219 | override fun deleteUsers() { 220 | 221 | roomDataSource.getUserDao().deleteUsers() 222 | } 223 | } 224 | 225 | ``` 226 | 227 | Tools used on the sample project 228 | ------------------------------------ 229 | * [Kotlin][6] 230 | * [Android Architecture Components][1] 231 | * [RxJava & RxAndroid][2] 232 | * [Dagger 2][3] 233 | * [Retrofit][4] 234 | * [OkHttp][5] 235 | 236 | [1]: https://developer.android.com/topic/libraries/architecture/adding-components.html 237 | [2]: https://github.com/ReactiveX/RxAndroid 238 | [3]: https://github.com/google/dagger 239 | [4]: https://github.com/square/retrofit 240 | [5]: https://github.com/square/okhttp 241 | [6]: https://kotlinlang.org/ 242 | [7]: https://developer.android.com/topic/libraries/architecture/viewmodel#the_lifecycle_of_a_viewmodel 243 | [8]: https://developer.android.com/topic/libraries/architecture/room 244 | 245 | 246 | # Demo 247 | ![](media/capture.png) 248 | 249 | 250 | ### Resources to start with Kotlin on Android 251 | 252 | * [Getting started with Android and Kotlin by Jetbrains][9] 253 | * [Get Started with Kotlin on Android by Google][10] 254 | * [Kotlin Lang Reference][11] 255 | * [Kotlin Blog by Jetbrains][12] 256 | * [Kotlin Kapt Annotation processing][13] 257 | * [Kotlin for Android Developers by Antonio Leiva][14] 258 | 259 | [9]: https://kotlinlang.org/docs/tutorials/kotlin-android.html 260 | [10]: https://developer.android.com/kotlin/get-started.html 261 | [11]: https://kotlinlang.org/docs/reference/ 262 | [12]: https://blog.jetbrains.com/kotlin/ 263 | [13]: https://kotlinlang.org/docs/reference/kapt.html 264 | [14]: https://antonioleiva.com/kotlin-android-developers-book/ 265 | 266 | 267 | ### Resources to start with Android Architecture Components 268 | 269 | * [Android Architecture Components][15] 270 | * [Adding Components to your Project][16] 271 | * [Android Architecture Components Samples][17] 272 | * [Android Architecture Components CodeLabs][18] 273 | * [Android Conferences - Google I/O 2017][19] 274 | 275 | [15]: https://developer.android.com/topic/libraries/architecture/index.html 276 | [16]: https://developer.android.com/topic/libraries/architecture/adding-components.html 277 | [17]: https://github.com/googlesamples/android-architecture-components 278 | [18]: https://codelabs.developers.google.com/?cat=Android 279 | [19]: https://www.youtube.com/results?search_query=google+I%2FO+android+components 280 | 281 | Developed By 282 | ------------ 283 | 284 | * Ali DOUIRI - 285 | 286 | License 287 | ------- 288 | 289 | MIT License 290 | 291 | Copyright (c) 2018 Ali DOUIRI 292 | 293 | Permission is hereby granted, free of charge, to any person obtaining a copy 294 | of this software and associated documentation files (the "Software"), to deal 295 | in the Software without restriction, including without limitation the rights 296 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 297 | copies of the Software, and to permit persons to whom the Software is 298 | furnished to do so, subject to the following conditions: 299 | 300 | The above copyright notice and this permission notice shall be included in all 301 | copies or substantial portions of the Software. 302 | 303 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 304 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 305 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 306 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 307 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 308 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 309 | SOFTWARE. 310 | 311 | 312 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'kotlin-android-extensions' 5 | 6 | android { 7 | 8 | compileSdkVersion build_versions.target_sdk 9 | 10 | defaultConfig { 11 | applicationId "androidachitecturecomponent.example" 12 | minSdkVersion build_versions.min_sdk 13 | targetSdkVersion build_versions.target_sdk 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | multiDexEnabled true 18 | } 19 | 20 | buildTypes { 21 | 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | 27 | debug{ 28 | 29 | buildConfigField "String", "BASE_URL", "\"https://api.github.com/\"" 30 | 31 | } 32 | } 33 | 34 | /** 35 | * if you will mix Java and Kotlin files in the same project is a good practice create a kotlin directory 36 | * but I prefer created the kotlin package although I will not mix them 37 | **/ 38 | sourceSets { 39 | main.java.srcDirs += 'src/main/kotlin' 40 | } 41 | 42 | buildToolsVersion '27.0.3' 43 | } 44 | 45 | dependencies { 46 | 47 | implementation fileTree(dir: 'libs', include: ['*.jar']) 48 | testImplementation "junit:junit:$versions.junit" 49 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 50 | androidTestImplementation "com.android.support.test.espresso:espresso-core:$versions.espresso" 51 | 52 | implementation "com.android.support:design:$versions.support" 53 | implementation "com.android.support:recyclerview-v7:$versions.support" 54 | implementation "com.android.support:cardview-v7:$versions.support" 55 | implementation "com.android.support.constraint:constraint-layout:$versions.constraint_layout" 56 | 57 | // Kotlin 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$versions.kotlin_version" 59 | compile "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin_version" 60 | 61 | // Java Rx 62 | implementation "io.reactivex:rxandroid:$versions.rx_android" 63 | implementation "io.reactivex:rxjava:$versions.rxjava" 64 | 65 | // LifeCycle 66 | compile "android.arch.lifecycle:extensions:$versions.archVersion" 67 | kapt "android.arch.lifecycle:compiler:$versions.archVersion" 68 | 69 | // Room 70 | implementation "android.arch.persistence.room:runtime:$versions.archVersion" 71 | kapt "android.arch.persistence.room:compiler:$versions.archVersion" 72 | 73 | // Retrofit 74 | implementation "com.squareup.retrofit2:retrofit:$versions.retrofit" 75 | implementation "com.squareup.retrofit2:adapter-rxjava:$versions.retrofit" 76 | implementation "com.squareup.retrofit2:converter-gson:$versions.retrofit" 77 | 78 | // Dagger 79 | implementation "com.google.dagger:dagger:$versions.dagger" 80 | kapt "com.google.dagger:dagger-compiler:$versions.dagger" 81 | 82 | // Glide 83 | implementation "com.github.bumptech.glide:glide:$versions.glide" 84 | kapt "com.github.bumptech.glide:compiler:$versions.glide" 85 | 86 | 87 | } 88 | repositories { 89 | mavenCentral() 90 | } 91 | 92 | -------------------------------------------------------------------------------- /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/androidachitecturecomponent/example/com/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.com; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("architecturecomponentkotlin.dialtechnologies.dialcomponentarchitectutekotlin", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/App.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.support.multidex.MultiDex 6 | import androidachitecturecomponent.example.data.repository.RepositoryDataSource 7 | import androidachitecturecomponent.example.di.AppComponent 8 | import androidachitecturecomponent.example.di.AppModule 9 | import androidachitecturecomponent.example.di.DaggerAppComponent 10 | import javax.inject.Inject 11 | 12 | 13 | /** 14 | * Created by DOUIRI Ali on 16/03/2018. 15 | * my.alidouiri@gmail.com 16 | */ 17 | class App : Application () { 18 | 19 | @Inject 20 | lateinit var mRepositoryDataSource : RepositoryDataSource 21 | 22 | companion object { 23 | 24 | lateinit var mAppComponent: AppComponent 25 | } 26 | 27 | init { 28 | 29 | initializeDagger() 30 | } 31 | 32 | override fun onCreate() { 33 | super.onCreate() 34 | 35 | mAppComponent.inject(this) 36 | 37 | //Get users from api 38 | mRepositoryDataSource.getUsersFromApi() 39 | } 40 | 41 | /** 42 | * Initialize AppComponent 43 | */ 44 | fun initializeDagger (){ 45 | 46 | mAppComponent = DaggerAppComponent.builder() 47 | .appModule(AppModule(this)) 48 | .build() 49 | } 50 | 51 | override fun attachBaseContext(base: Context?) { 52 | super.attachBaseContext(base) 53 | MultiDex.install(this) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/local/LocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.room 2 | 3 | import android.arch.persistence.room.Database 4 | import android.arch.persistence.room.RoomDatabase 5 | import javax.inject.Singleton 6 | 7 | 8 | /** 9 | * Created by DOUIRI Ali on 15/03/2018. 10 | * my.alidouiri@gmail.com 11 | */ 12 | 13 | @Singleton 14 | @Database(entities = arrayOf(UserEntity::class), version = 1) 15 | abstract class LocalDataSource : RoomDatabase() { 16 | 17 | abstract fun getUserDao() : UserDao 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/local/UserDao.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.room 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.arch.persistence.room.Dao 5 | import android.arch.persistence.room.Insert 6 | import android.arch.persistence.room.OnConflictStrategy 7 | import android.arch.persistence.room.Query 8 | 9 | 10 | /** 11 | * Created by DOUIRI Ali on 15/03/2018. 12 | * my.alidouiri@gmail.com 13 | */ 14 | @Dao 15 | interface UserDao { 16 | 17 | @Query("SELECT * FROM users") 18 | fun getUsers() : LiveData> 19 | 20 | @Insert(onConflict = OnConflictStrategy.REPLACE) 21 | fun saveUsers(users : List) 22 | 23 | @Query("DELETE FROM users") 24 | fun deleteUsers() 25 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/local/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.room 2 | 3 | import android.arch.persistence.room.Entity 4 | import android.arch.persistence.room.PrimaryKey 5 | 6 | 7 | /** 8 | * Created by DOUIRI Ali on 15/03/2018. 9 | * my.alidouiri@gmail.com 10 | */ 11 | 12 | @Entity(tableName = "users") 13 | data class UserEntity( 14 | 15 | @PrimaryKey(autoGenerate = true) 16 | var id : Long = 0, 17 | var name : String, 18 | var avatar : String 19 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/remote/RemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.remote 2 | 3 | import retrofit2.Response 4 | import rx.Observable 5 | 6 | 7 | /** 8 | * Created by DOUIRI Ali on 15/03/2018. 9 | * my.alidouiri@gmail.com 10 | */ 11 | 12 | class RemoteDataSource constructor(private val remoteService: RemoteService) { 13 | 14 | fun getUsersFromApi() : Observable>> = remoteService.getUsersFromApi() 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/remote/RemoteService.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.remote 2 | 3 | import retrofit2.Response 4 | import retrofit2.http.GET 5 | import rx.Observable 6 | 7 | 8 | /** 9 | * Created by DOUIRI Ali on 15/03/2018. 10 | * my.alidouiri@gmail.com 11 | */ 12 | interface RemoteService { 13 | 14 | @GET("users") 15 | fun getUsersFromApi() : Observable>> 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/remote/UserResponse.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.remote 2 | 3 | import androidachitecturecomponent.example.data.room.UserEntity 4 | import com.google.gson.annotations.SerializedName 5 | 6 | 7 | /** 8 | * Created by DOUIRI Ali on 15/03/2018. 9 | * my.alidouiri@gmail.com 10 | */ 11 | data class UserResponse ( 12 | 13 | @SerializedName("login") 14 | private var mName : String, 15 | @SerializedName("avatar_url") 16 | private var mAvatar : String 17 | ){ 18 | 19 | fun toUserEntity() = 20 | UserEntity(0, mName, mAvatar) 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/repository/Repository.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.repository 2 | 3 | import android.arch.lifecycle.LiveData 4 | import androidachitecturecomponent.example.data.room.UserEntity 5 | 6 | 7 | /** 8 | * Created by DOUIRI Ali on 15/03/2018. 9 | * my.alidouiri@gmail.com 10 | */ 11 | interface Repository { 12 | 13 | fun getUsersFromDb() : LiveData> 14 | 15 | fun getUsersFromApi() 16 | 17 | fun saveUsers(users : List) 18 | 19 | fun deleteUsers() 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/data/repository/RepositoryDataSource.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.data.repository 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.support.annotation.NonNull 5 | import android.util.Log 6 | import androidachitecturecomponent.example.data.remote.RemoteDataSource 7 | import androidachitecturecomponent.example.data.room.LocalDataSource 8 | import androidachitecturecomponent.example.data.room.UserEntity 9 | import androidachitecturecomponent.example.utils.Transformation 10 | import rx.Completable 11 | import rx.CompletableSubscriber 12 | import rx.Subscription 13 | import rx.android.schedulers.AndroidSchedulers 14 | import rx.schedulers.Schedulers 15 | 16 | 17 | /** 18 | * Created by DOUIRI Ali on 15/03/2018. 19 | * my.alidouiri@gmail.com 20 | */ 21 | class RepositoryDataSource constructor(private val remoteDataSource: RemoteDataSource, 22 | private val roomDataSource: LocalDataSource) : Repository { 23 | 24 | private val TAG : String = RepositoryDataSource::class.java.simpleName 25 | 26 | /** 27 | * Get users from database 28 | */ 29 | override fun getUsersFromDb(): LiveData> = roomDataSource.getUserDao().getUsers() 30 | 31 | /** 32 | * Get users from api 33 | */ 34 | override fun getUsersFromApi() { 35 | 36 | remoteDataSource.getUsersFromApi() 37 | .subscribeOn(Schedulers.io()) 38 | .observeOn(AndroidSchedulers.mainThread()) 39 | .subscribe({ result -> 40 | 41 | Completable.fromAction { 42 | deleteUsers() 43 | saveUsers(Transformation.toUsersEtities(result.body()!!)) 44 | } 45 | .subscribeOn(Schedulers.io()) 46 | .observeOn(AndroidSchedulers.mainThread()) 47 | .subscribe(object : CompletableSubscriber { 48 | override fun onSubscribe(@NonNull subscription : Subscription) { 49 | 50 | } 51 | 52 | override fun onCompleted() { 53 | Log.i(TAG, "DataSource has been Populated") 54 | } 55 | 56 | override fun onError(@NonNull e: Throwable) { 57 | e.printStackTrace() 58 | Log.e(TAG, "DataSource hasn't been Populated") 59 | } 60 | }) 61 | 62 | }, 63 | { error -> Log.e(TAG, "{$error.message}") }, 64 | { Log.d(TAG, "completed") }) 65 | } 66 | 67 | /** 68 | * Save users into database 69 | */ 70 | override fun saveUsers(users: List) { 71 | 72 | roomDataSource.getUserDao().saveUsers(users) 73 | } 74 | 75 | /** 76 | * Delete all users 77 | */ 78 | override fun deleteUsers() { 79 | 80 | roomDataSource.getUserDao().deleteUsers() 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.di 2 | 3 | import androidachitecturecomponent.example.App 4 | import androidachitecturecomponent.example.ui.user.UserViewModel 5 | import dagger.Component 6 | import javax.inject.Singleton 7 | 8 | 9 | /** 10 | * Created by DOUIRI Ali on 16/03/2018. 11 | * my.alidouiri@gmail.com 12 | */ 13 | 14 | @Singleton 15 | @Component(modules = arrayOf(AppModule::class)) 16 | interface AppComponent { 17 | 18 | fun inject (viewModel : UserViewModel) 19 | 20 | fun inject (dialApp : App) 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.di 2 | 3 | import android.app.Application 4 | import android.arch.persistence.room.Room 5 | import android.content.Context 6 | import androidachitecturecomponent.example.BuildConfig 7 | import androidachitecturecomponent.example.data.remote.RemoteDataSource 8 | import androidachitecturecomponent.example.data.remote.RemoteService 9 | import androidachitecturecomponent.example.data.repository.RepositoryDataSource 10 | import androidachitecturecomponent.example.data.room.LocalDataSource 11 | import androidachitecturecomponent.example.data.room.UserDao 12 | import dagger.Module 13 | import dagger.Provides 14 | import retrofit2.Retrofit 15 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 16 | import retrofit2.converter.gson.GsonConverterFactory 17 | import javax.inject.Singleton 18 | 19 | /** 20 | * Created by DOUIRI Ali on 16/03/2018. 21 | * my.alidouiri@gmail.com 22 | */ 23 | 24 | @Module 25 | @Singleton 26 | class AppModule { 27 | 28 | var mApplication : Application ?= null 29 | 30 | constructor(application : Application) { 31 | 32 | mApplication = application 33 | } 34 | 35 | @Singleton 36 | @Provides 37 | fun providesRoomDataSource(): LocalDataSource { 38 | return Room.databaseBuilder(mApplication!!, LocalDataSource::class.java!!, "user_database") 39 | .build() 40 | } 41 | 42 | @Singleton 43 | @Provides 44 | fun providesUserDao(userRoomDatabase: LocalDataSource): UserDao { 45 | return userRoomDatabase.getUserDao() 46 | } 47 | 48 | @Provides 49 | fun providesAppContext(): Context { 50 | return mApplication!! 51 | } 52 | 53 | @Provides 54 | @Singleton 55 | fun providesRemoteService(): RemoteService { 56 | return Retrofit.Builder() 57 | .baseUrl(BuildConfig.BASE_URL) 58 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 59 | .addConverterFactory(GsonConverterFactory.create()) 60 | .build() 61 | .create(RemoteService::class.java!!) 62 | } 63 | 64 | @Provides 65 | @Singleton 66 | fun providesRemoteDataSource(remoteService: RemoteService): RemoteDataSource { 67 | return RemoteDataSource(remoteService) 68 | } 69 | 70 | @Provides 71 | @Singleton 72 | fun providesUserRepository(remoteDataSource: RemoteDataSource, roomDataSource: LocalDataSource): RepositoryDataSource { 73 | 74 | return RepositoryDataSource(remoteDataSource, roomDataSource) 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.ui 2 | 3 | import android.os.Bundle 4 | import android.support.v4.app.Fragment 5 | import android.support.v7.app.AppCompatActivity 6 | import androidachitecturecomponent.example.R 7 | import androidachitecturecomponent.example.ui.user.UsersFragment 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.activity_main) 15 | 16 | if(savedInstanceState == null) 17 | replaceFragment(UsersFragment.newInstance()) 18 | 19 | } 20 | 21 | private fun replaceFragment(fragment: Fragment) { 22 | supportFragmentManager.beginTransaction() 23 | .replace(R.id.container, fragment) 24 | .commit() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/ui/user/UserAdapter.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.ui.user 2 | 3 | import android.content.Context 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidachitecturecomponent.example.R 9 | import androidachitecturecomponent.example.data.room.UserEntity 10 | import com.bumptech.glide.Glide 11 | import kotlinx.android.synthetic.main.item_view.view.* 12 | 13 | 14 | /** 15 | * Created by DOUIRI Ali on 15/03/2018. 16 | * my.alidouiri@gmail.com 17 | */ 18 | 19 | class UserAdapter (private var context : Context ? , private var users : List) : 20 | RecyclerView.Adapter() { 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 23 | 24 | val v = LayoutInflater.from(parent?.context).inflate(R.layout.item_view, parent, false) 25 | return ViewHolder(v) 26 | 27 | } 28 | 29 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 30 | 31 | holder?.mName?.text = users[position].name 32 | Glide.with(context).asBitmap().load(users[position].avatar).into(holder?.mAvatar) 33 | 34 | } 35 | 36 | override fun getItemCount(): Int = users?.size 37 | 38 | class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){ 39 | val mName = itemView.name 40 | val mAvatar = itemView.avatar 41 | 42 | } 43 | 44 | fun setItems(users : List) { 45 | 46 | this.users = users 47 | notifyDataSetChanged() 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/ui/user/UserViewModel.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.ui.user 2 | 3 | import android.arch.lifecycle.* 4 | import androidachitecturecomponent.example.App 5 | import androidachitecturecomponent.example.data.repository.RepositoryDataSource 6 | import androidachitecturecomponent.example.data.room.UserEntity 7 | import javax.inject.Inject 8 | 9 | 10 | /** 11 | * Created by DOUIRI Ali on 15/03/2018. 12 | * my.alidouiri@gmail.com 13 | */ 14 | class UserViewModel : ViewModel(), LifecycleObserver { 15 | 16 | private val TAG : String ?= UserViewModel::class.simpleName 17 | 18 | @Inject 19 | lateinit var mRepositoryDataSource : RepositoryDataSource 20 | 21 | init { 22 | 23 | App.mAppComponent.inject(this) 24 | } 25 | 26 | 27 | /** 28 | * Get users from database 29 | */ 30 | fun getUsers() : LiveData> = mRepositoryDataSource.getUsersFromDb() 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/ui/user/UsersFragment.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.ui.user 2 | 3 | import android.arch.lifecycle.Observer 4 | import android.arch.lifecycle.ViewModelProviders 5 | import android.os.Bundle 6 | import android.support.v4.app.Fragment 7 | import android.support.v7.widget.DefaultItemAnimator 8 | import android.support.v7.widget.LinearLayoutManager 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import androidachitecturecomponent.example.R 13 | import androidachitecturecomponent.example.data.room.UserEntity 14 | import kotlinx.android.synthetic.main.fragment_users.* 15 | 16 | /** 17 | * Created by DOUIRI Ali on 15/03/2018. 18 | * my.alidouiri@gmail.com 19 | */ 20 | class UsersFragment : Fragment() { 21 | 22 | private val TAG = UsersFragment::class.java.simpleName 23 | 24 | private var mUserViewModel : UserViewModel ?= null 25 | private var mAdapter : UserAdapter ?= null 26 | 27 | companion object { 28 | 29 | fun newInstance() : UsersFragment = UsersFragment() 30 | } 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | initViewModel() 35 | } 36 | 37 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 38 | return inflater.inflate(R.layout.fragment_users, container, false) 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | initViews() 44 | populate() 45 | } 46 | 47 | 48 | private fun initViews (){ 49 | 50 | mAdapter = UserAdapter(activity, arrayListOf()) 51 | val mLayoutManager = LinearLayoutManager(activity) 52 | recyclerview.layoutManager = mLayoutManager 53 | recyclerview.itemAnimator = DefaultItemAnimator() 54 | recyclerview.adapter = mAdapter 55 | 56 | } 57 | 58 | /** 59 | * Initialize [UserViewModel] 60 | */ 61 | private fun initViewModel(){ 62 | 63 | mUserViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java) 64 | mUserViewModel?.let { lifecycle.addObserver(it) } 65 | 66 | } 67 | 68 | /** 69 | * Show users 70 | */ 71 | private fun populate() { 72 | 73 | mUserViewModel?.getUsers()?.observe(this, 74 | Observer { 75 | if (it != null) { 76 | mAdapter?.setItems(it) 77 | } 78 | }) 79 | } 80 | 81 | override fun onDestroy() { 82 | super.onDestroy() 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/androidachitecturecomponent/example/utils/Transformation.kt: -------------------------------------------------------------------------------- 1 | package androidachitecturecomponent.example.utils 2 | 3 | import androidachitecturecomponent.example.data.remote.UserResponse 4 | import androidachitecturecomponent.example.data.room.UserEntity 5 | 6 | /** 7 | * Created by DOUIRI Ali on 15/03/2018. 8 | * my.alidouiri@gmail.com 9 | * 10 | * 11 | * This class makes it possible to transform response of api to [UserEntity] 12 | * 13 | */ 14 | class Transformation { 15 | 16 | companion object { 17 | 18 | fun toUsersEtities(usersResponse: List): List = usersResponse.map { it.toUserEntity() } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_users.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 23 | 24 | -------------------------------------------------------------------------------- /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/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/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 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidArchitectureComponentKotlin 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 16 | 17 |