├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
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 | 
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 | 
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 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/test/java/androidachitecturecomponent/example/com/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package androidachitecturecomponent.example.com;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | apply from: 'versions.gradle'
6 |
7 | repositories {
8 | google()
9 | jcenter()
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.1.0'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin_version"
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | jcenter()
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 30 10:52:25 WEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/media/architecture_component.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/media/architecture_component.png
--------------------------------------------------------------------------------
/media/capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DouiriAli/android-architecture-components-kotlin/2dcc33fca0961b4de478eea0e30442aa668e7dde/media/capture.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/versions.gradle:
--------------------------------------------------------------------------------
1 | def versions = [:]
2 | versions.support = "27.0.2"
3 | versions.dagger = "2.11"
4 | versions.junit = "4.12"
5 | versions.espresso = "3.0.1"
6 | versions.retrofit = "2.3.0"
7 | versions.constraint_layout = "1.0.2"
8 | versions.glide = "4.4.0"
9 | versions.android_gradle_plugin = "3.0.1"
10 | versions.rxjava = "1.1.6"
11 | versions.rx_android = "1.2.1"
12 | versions.archVersion = '1.0.0'
13 | versions.kotlin_version = '1.2.30'
14 | ext.versions = versions
15 |
16 | def build_versions = [:]
17 | build_versions.min_sdk = 15
18 | build_versions.target_sdk = 27
19 | build_versions.build_tools = "27.0.2"
20 | ext.build_versions = build_versions
21 |
22 |
--------------------------------------------------------------------------------