├── .gitignore ├── .idea └── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── huawei │ │ └── pagingexampleproject │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── huawei │ │ │ └── pagingexampleproject │ │ │ ├── App.kt │ │ │ ├── common │ │ │ ├── BaseUiState.kt │ │ │ ├── BindingAdapters.kt │ │ │ ├── FooterAdapter.kt │ │ │ ├── FooterUiState.kt │ │ │ └── FooterViewHolder.kt │ │ │ ├── data │ │ │ ├── model │ │ │ │ ├── NameModel.kt │ │ │ │ ├── PictureModel.kt │ │ │ │ ├── UserModel.kt │ │ │ │ └── UserResponse.kt │ │ │ ├── pagingdatasource │ │ │ │ └── UserPagingDataSource.kt │ │ │ └── repository │ │ │ │ ├── UserRepository.kt │ │ │ │ └── UserRepositoryImpl.kt │ │ │ ├── di │ │ │ ├── NetworkModule.kt │ │ │ └── RepositoryModule.kt │ │ │ ├── network │ │ │ └── UserService.kt │ │ │ ├── ui │ │ │ ├── ItemUserUiState.kt │ │ │ ├── UserActivity.kt │ │ │ ├── UserViewHolder.kt │ │ │ ├── UserViewModel.kt │ │ │ ├── UsersAdapter.kt │ │ │ └── UsersUiState.kt │ │ │ └── util │ │ │ ├── NetworkConnectionInterceptor.kt │ │ │ ├── NetworkUtils.kt │ │ │ └── ext │ │ │ ├── LifecycleOwnerExt.kt │ │ │ └── ViewDataBindingExt.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_user.xml │ │ ├── item_paging_footer.xml │ │ └── item_user.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ ├── strings.xml │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── huawei │ └── pagingexampleproject │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/androidstudio,android,kotlin,java 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,android,kotlin,java 4 | 5 | ### Android ### 6 | # Built application files 7 | *.apk 8 | *.aar 9 | *.ap_ 10 | *.aab 11 | 12 | # Files for the ART/Dalvik VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Generated files 19 | bin/ 20 | gen/ 21 | out/ 22 | # Uncomment the following line in case you need and you don't have the release build type files in your app 23 | # release/ 24 | 25 | # Gradle files 26 | .gradle/ 27 | build/ 28 | 29 | # Local configuration file (sdk path, etc) 30 | local.properties 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Log Files 36 | *.log 37 | 38 | # Android Studio Navigation editor temp files 39 | .navigation/ 40 | 41 | # Android Studio captures folder 42 | captures/ 43 | 44 | # IntelliJ 45 | *.iml 46 | .idea/workspace.xml 47 | .idea/tasks.xml 48 | .idea/gradle.xml 49 | .idea/assetWizardSettings.xml 50 | .idea/dictionaries 51 | .idea/libraries 52 | .idea/jarRepositories.xml 53 | # Android Studio 3 in .gitignore file. 54 | .idea/caches 55 | .idea/modules.xml 56 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 57 | .idea/navEditor.xml 58 | 59 | # Keystore files 60 | # Uncomment the following lines if you do not want to check your keystore files in. 61 | #*.jks 62 | #*.keystore 63 | 64 | # External native build folder generated in Android Studio 2.2 and later 65 | .externalNativeBuild 66 | .cxx/ 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | # google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | 83 | # Version control 84 | vcs.xml 85 | 86 | # lint 87 | lint/intermediates/ 88 | lint/generated/ 89 | lint/outputs/ 90 | lint/tmp/ 91 | # lint/reports/ 92 | 93 | # Android Profiling 94 | *.hprof 95 | 96 | ### Android Patch ### 97 | gen-external-apklibs 98 | output.json 99 | 100 | # Replacement of .externalNativeBuild directories introduced 101 | # with Android Studio 3.5. 102 | 103 | ### Java ### 104 | # Compiled class file 105 | 106 | # Log file 107 | 108 | # BlueJ files 109 | *.ctxt 110 | 111 | # Mobile Tools for Java (J2ME) 112 | .mtj.tmp/ 113 | 114 | # Package Files # 115 | *.jar 116 | *.war 117 | *.nar 118 | *.ear 119 | *.zip 120 | *.tar.gz 121 | *.rar 122 | 123 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 124 | hs_err_pid* 125 | 126 | ### Kotlin ### 127 | # Compiled class file 128 | 129 | # Log file 130 | 131 | # BlueJ files 132 | 133 | # Mobile Tools for Java (J2ME) 134 | 135 | # Package Files # 136 | 137 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 138 | 139 | ### AndroidStudio ### 140 | # Covers files to be ignored for android development using Android Studio. 141 | 142 | # Built application files 143 | 144 | # Files for the ART/Dalvik VM 145 | 146 | # Java class files 147 | 148 | # Generated files 149 | 150 | # Gradle files 151 | .gradle 152 | 153 | # Signing files 154 | .signing/ 155 | 156 | # Local configuration file (sdk path, etc) 157 | 158 | # Proguard folder generated by Eclipse 159 | 160 | # Log Files 161 | 162 | # Android Studio 163 | /*/build/ 164 | /*/local.properties 165 | /*/out 166 | /*/*/build 167 | /*/*/production 168 | *.ipr 169 | *~ 170 | *.swp 171 | 172 | # Keystore files 173 | *.jks 174 | *.keystore 175 | 176 | # Google Services (e.g. APIs or Firebase) 177 | # google-services.json 178 | 179 | # Android Patch 180 | 181 | # External native build folder generated in Android Studio 2.2 and later 182 | 183 | # NDK 184 | obj/ 185 | 186 | # IntelliJ IDEA 187 | *.iws 188 | /out/ 189 | 190 | # User-specific configurations 191 | .idea/caches/ 192 | .idea/libraries/ 193 | .idea/shelf/ 194 | .idea/.name 195 | .idea/compiler.xml 196 | .idea/copyright/profiles_settings.xml 197 | .idea/encodings.xml 198 | .idea/misc.xml 199 | .idea/scopes/scope_settings.xml 200 | .idea/vcs.xml 201 | .idea/jsLibraryMappings.xml 202 | .idea/datasources.xml 203 | .idea/dataSources.ids 204 | .idea/sqlDataSources.xml 205 | .idea/dynamic.xml 206 | .idea/uiDesigner.xml 207 | 208 | # OS-specific files 209 | .DS_Store 210 | .DS_Store? 211 | ._* 212 | .Spotlight-V100 213 | .Trashes 214 | ehthumbs.db 215 | Thumbs.db 216 | 217 | # Legacy Eclipse project files 218 | .classpath 219 | .project 220 | .cproject 221 | .settings/ 222 | 223 | # Mobile Tools for Java (J2ME) 224 | 225 | # Package Files # 226 | 227 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 228 | 229 | ## Plugin-specific files: 230 | 231 | # mpeltonen/sbt-idea plugin 232 | .idea_modules/ 233 | 234 | # JIRA plugin 235 | atlassian-ide-plugin.xml 236 | 237 | # Mongo Explorer plugin 238 | .idea/mongoSettings.xml 239 | 240 | # Crashlytics plugin (for Android Studio and IntelliJ) 241 | com_crashlytics_export_strings.xml 242 | crashlytics.properties 243 | crashlytics-build.properties 244 | fabric.properties 245 | 246 | ### AndroidStudio Patch ### 247 | 248 | !/gradle/wrapper/gradle-wrapper.jar 249 | 250 | # End of https://www.toptal.com/developers/gitignore/api/androidstudio,android,kotlin,java 251 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/androidstudio,android,kotlin,java 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,android,kotlin,java 4 | 5 | ### Android ### 6 | # Built application files 7 | *.apk 8 | *.aar 9 | *.ap_ 10 | *.aab 11 | 12 | # Files for the ART/Dalvik VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Generated files 19 | bin/ 20 | gen/ 21 | out/ 22 | # Uncomment the following line in case you need and you don't have the release build type files in your app 23 | # release/ 24 | 25 | # Gradle files 26 | .gradle/ 27 | build/ 28 | 29 | # Local configuration file (sdk path, etc) 30 | local.properties 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Log Files 36 | *.log 37 | 38 | # Android Studio Navigation editor temp files 39 | .navigation/ 40 | 41 | # Android Studio captures folder 42 | captures/ 43 | 44 | # IntelliJ 45 | *.iml 46 | .idea/workspace.xml 47 | .idea/tasks.xml 48 | .idea/gradle.xml 49 | .idea/assetWizardSettings.xml 50 | .idea/dictionaries 51 | .idea/libraries 52 | .idea/jarRepositories.xml 53 | # Android Studio 3 in .gitignore file. 54 | .idea/caches 55 | .idea/modules.xml 56 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 57 | .idea/navEditor.xml 58 | 59 | # Keystore files 60 | # Uncomment the following lines if you do not want to check your keystore files in. 61 | #*.jks 62 | #*.keystore 63 | 64 | # External native build folder generated in Android Studio 2.2 and later 65 | .externalNativeBuild 66 | .cxx/ 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | # google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | 83 | # Version control 84 | vcs.xml 85 | 86 | # lint 87 | lint/intermediates/ 88 | lint/generated/ 89 | lint/outputs/ 90 | lint/tmp/ 91 | # lint/reports/ 92 | 93 | # Android Profiling 94 | *.hprof 95 | 96 | ### Android Patch ### 97 | gen-external-apklibs 98 | output.json 99 | 100 | # Replacement of .externalNativeBuild directories introduced 101 | # with Android Studio 3.5. 102 | 103 | ### Java ### 104 | # Compiled class file 105 | 106 | # Log file 107 | 108 | # BlueJ files 109 | *.ctxt 110 | 111 | # Mobile Tools for Java (J2ME) 112 | .mtj.tmp/ 113 | 114 | # Package Files # 115 | *.jar 116 | *.war 117 | *.nar 118 | *.ear 119 | *.zip 120 | *.tar.gz 121 | *.rar 122 | 123 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 124 | hs_err_pid* 125 | 126 | ### Kotlin ### 127 | # Compiled class file 128 | 129 | # Log file 130 | 131 | # BlueJ files 132 | 133 | # Mobile Tools for Java (J2ME) 134 | 135 | # Package Files # 136 | 137 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 138 | 139 | ### AndroidStudio ### 140 | # Covers files to be ignored for android development using Android Studio. 141 | 142 | # Built application files 143 | 144 | # Files for the ART/Dalvik VM 145 | 146 | # Java class files 147 | 148 | # Generated files 149 | 150 | # Gradle files 151 | .gradle 152 | 153 | # Signing files 154 | .signing/ 155 | 156 | # Local configuration file (sdk path, etc) 157 | 158 | # Proguard folder generated by Eclipse 159 | 160 | # Log Files 161 | 162 | # Android Studio 163 | /*/build/ 164 | /*/local.properties 165 | /*/out 166 | /*/*/build 167 | /*/*/production 168 | *.ipr 169 | *~ 170 | *.swp 171 | 172 | # Keystore files 173 | *.jks 174 | *.keystore 175 | 176 | # Google Services (e.g. APIs or Firebase) 177 | # google-services.json 178 | 179 | # Android Patch 180 | 181 | # External native build folder generated in Android Studio 2.2 and later 182 | 183 | # NDK 184 | obj/ 185 | 186 | # IntelliJ IDEA 187 | *.iws 188 | /out/ 189 | 190 | # User-specific configurations 191 | .idea/caches/ 192 | .idea/libraries/ 193 | .idea/shelf/ 194 | .idea/.name 195 | .idea/compiler.xml 196 | .idea/copyright/profiles_settings.xml 197 | .idea/encodings.xml 198 | .idea/misc.xml 199 | .idea/scopes/scope_settings.xml 200 | .idea/vcs.xml 201 | .idea/jsLibraryMappings.xml 202 | .idea/datasources.xml 203 | .idea/dataSources.ids 204 | .idea/sqlDataSources.xml 205 | .idea/dynamic.xml 206 | .idea/uiDesigner.xml 207 | 208 | # OS-specific files 209 | .DS_Store 210 | .DS_Store? 211 | ._* 212 | .Spotlight-V100 213 | .Trashes 214 | ehthumbs.db 215 | Thumbs.db 216 | 217 | # Legacy Eclipse project files 218 | .classpath 219 | .project 220 | .cproject 221 | .settings/ 222 | 223 | # Mobile Tools for Java (J2ME) 224 | 225 | # Package Files # 226 | 227 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 228 | 229 | ## Plugin-specific files: 230 | 231 | # mpeltonen/sbt-idea plugin 232 | .idea_modules/ 233 | 234 | # JIRA plugin 235 | atlassian-ide-plugin.xml 236 | 237 | # Mongo Explorer plugin 238 | .idea/mongoSettings.xml 239 | 240 | # Crashlytics plugin (for Android Studio and IntelliJ) 241 | com_crashlytics_export_strings.xml 242 | crashlytics.properties 243 | crashlytics-build.properties 244 | fabric.properties 245 | 246 | ### AndroidStudio Patch ### 247 | 248 | !/gradle/wrapper/gradle-wrapper.jar 249 | 250 | # End of https://www.toptal.com/developers/gitignore/api/androidstudio,android,kotlin,java -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/androidstudio,android,kotlin,java 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio,android,kotlin,java 4 | 5 | ### Android ### 6 | # Built application files 7 | *.apk 8 | *.aar 9 | *.ap_ 10 | *.aab 11 | 12 | # Files for the ART/Dalvik VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Generated files 19 | bin/ 20 | gen/ 21 | out/ 22 | # Uncomment the following line in case you need and you don't have the release build type files in your app 23 | # release/ 24 | 25 | # Gradle files 26 | .gradle/ 27 | build/ 28 | 29 | # Local configuration file (sdk path, etc) 30 | local.properties 31 | 32 | # Proguard folder generated by Eclipse 33 | proguard/ 34 | 35 | # Log Files 36 | *.log 37 | 38 | # Android Studio Navigation editor temp files 39 | .navigation/ 40 | 41 | # Android Studio captures folder 42 | captures/ 43 | 44 | # IntelliJ 45 | *.iml 46 | .idea/workspace.xml 47 | .idea/tasks.xml 48 | .idea/gradle.xml 49 | .idea/assetWizardSettings.xml 50 | .idea/dictionaries 51 | .idea/libraries 52 | .idea/jarRepositories.xml 53 | # Android Studio 3 in .gitignore file. 54 | .idea/caches 55 | .idea/modules.xml 56 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 57 | .idea/navEditor.xml 58 | 59 | # Keystore files 60 | # Uncomment the following lines if you do not want to check your keystore files in. 61 | #*.jks 62 | #*.keystore 63 | 64 | # External native build folder generated in Android Studio 2.2 and later 65 | .externalNativeBuild 66 | .cxx/ 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | # google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | 83 | # Version control 84 | vcs.xml 85 | 86 | # lint 87 | lint/intermediates/ 88 | lint/generated/ 89 | lint/outputs/ 90 | lint/tmp/ 91 | # lint/reports/ 92 | 93 | # Android Profiling 94 | *.hprof 95 | 96 | ### Android Patch ### 97 | gen-external-apklibs 98 | output.json 99 | 100 | # Replacement of .externalNativeBuild directories introduced 101 | # with Android Studio 3.5. 102 | 103 | ### Java ### 104 | # Compiled class file 105 | 106 | # Log file 107 | 108 | # BlueJ files 109 | *.ctxt 110 | 111 | # Mobile Tools for Java (J2ME) 112 | .mtj.tmp/ 113 | 114 | # Package Files # 115 | *.jar 116 | *.war 117 | *.nar 118 | *.ear 119 | *.zip 120 | *.tar.gz 121 | *.rar 122 | 123 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 124 | hs_err_pid* 125 | 126 | ### Kotlin ### 127 | # Compiled class file 128 | 129 | # Log file 130 | 131 | # BlueJ files 132 | 133 | # Mobile Tools for Java (J2ME) 134 | 135 | # Package Files # 136 | 137 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 138 | 139 | ### AndroidStudio ### 140 | # Covers files to be ignored for android development using Android Studio. 141 | 142 | # Built application files 143 | 144 | # Files for the ART/Dalvik VM 145 | 146 | # Java class files 147 | 148 | # Generated files 149 | 150 | # Gradle files 151 | .gradle 152 | 153 | # Signing files 154 | .signing/ 155 | 156 | # Local configuration file (sdk path, etc) 157 | 158 | # Proguard folder generated by Eclipse 159 | 160 | # Log Files 161 | 162 | # Android Studio 163 | /*/build/ 164 | /*/local.properties 165 | /*/out 166 | /*/*/build 167 | /*/*/production 168 | *.ipr 169 | *~ 170 | *.swp 171 | 172 | # Keystore files 173 | *.jks 174 | *.keystore 175 | 176 | # Google Services (e.g. APIs or Firebase) 177 | # google-services.json 178 | 179 | # Android Patch 180 | 181 | # External native build folder generated in Android Studio 2.2 and later 182 | 183 | # NDK 184 | obj/ 185 | 186 | # IntelliJ IDEA 187 | *.iws 188 | /out/ 189 | 190 | # User-specific configurations 191 | .idea/caches/ 192 | .idea/libraries/ 193 | .idea/shelf/ 194 | .idea/.name 195 | .idea/compiler.xml 196 | .idea/copyright/profiles_settings.xml 197 | .idea/encodings.xml 198 | .idea/misc.xml 199 | .idea/scopes/scope_settings.xml 200 | .idea/vcs.xml 201 | .idea/jsLibraryMappings.xml 202 | .idea/datasources.xml 203 | .idea/dataSources.ids 204 | .idea/sqlDataSources.xml 205 | .idea/dynamic.xml 206 | .idea/uiDesigner.xml 207 | 208 | # OS-specific files 209 | .DS_Store 210 | .DS_Store? 211 | ._* 212 | .Spotlight-V100 213 | .Trashes 214 | ehthumbs.db 215 | Thumbs.db 216 | 217 | # Legacy Eclipse project files 218 | .classpath 219 | .project 220 | .cproject 221 | .settings/ 222 | 223 | # Mobile Tools for Java (J2ME) 224 | 225 | # Package Files # 226 | 227 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 228 | 229 | ## Plugin-specific files: 230 | 231 | # mpeltonen/sbt-idea plugin 232 | .idea_modules/ 233 | 234 | # JIRA plugin 235 | atlassian-ide-plugin.xml 236 | 237 | # Mongo Explorer plugin 238 | .idea/mongoSettings.xml 239 | 240 | # Crashlytics plugin (for Android Studio and IntelliJ) 241 | com_crashlytics_export_strings.xml 242 | crashlytics.properties 243 | crashlytics-build.properties 244 | fabric.properties 245 | 246 | ### AndroidStudio Patch ### 247 | 248 | !/gradle/wrapper/gradle-wrapper.jar 249 | 250 | # End of https://www.toptal.com/developers/gitignore/api/androidstudio,android,kotlin,java -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | } 7 | 8 | android { 9 | compileSdk 31 10 | 11 | defaultConfig { 12 | applicationId "com.huawei.pagingexampleproject" 13 | minSdk 21 14 | targetSdk 31 15 | versionCode 1 16 | versionName "1.0" 17 | buildConfigField "String", "API_URL", "\"https://randomuser.me/api/\"" 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildFeatures { 22 | dataBinding true 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | 31 | debug { 32 | minifyEnabled false 33 | } 34 | } 35 | compileOptions { 36 | sourceCompatibility JavaVersion.VERSION_1_8 37 | targetCompatibility JavaVersion.VERSION_1_8 38 | } 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | } 43 | 44 | dependencies { 45 | 46 | implementation 'androidx.core:core-ktx:1.7.0' 47 | implementation 'androidx.appcompat:appcompat:1.4.0' 48 | implementation 'com.google.android.material:material:1.4.0' 49 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2' 50 | testImplementation 'junit:junit:4.+' 51 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 53 | 54 | 55 | // OkHttp interceptor 56 | implementation "com.squareup.okhttp3:logging-interceptor:$okHttp_interceptor_version" 57 | 58 | // Retrofit 59 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version" 60 | implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" 61 | 62 | //Paging 63 | implementation "androidx.paging:paging-runtime-ktx:$paging_version" 64 | 65 | //Hilt 66 | implementation "com.google.dagger:hilt-android:$hilt_version" 67 | kapt "com.google.dagger:hilt-android-compiler:$hilt_version" 68 | 69 | //Glide 70 | implementation "com.github.bumptech.glide:glide:$glide_version" 71 | annotationProcessor "com.github.bumptech.glide:compiler:$glide_version" 72 | 73 | //Coroutines 74 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_core_version" 75 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version" 76 | 77 | //ktx 78 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ktx_version" 79 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_ktx_version" 80 | implementation "androidx.activity:activity-ktx:$activity_ktx_version" 81 | 82 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/huawei/pagingexampleproject/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.huawei.pagingexampleproject", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/App.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | /** 7 | * Created by Oguz Sahin on 11/10/2021. 8 | */ 9 | @HiltAndroidApp 10 | class App : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/common/BaseUiState.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.common 2 | 3 | import android.view.View 4 | 5 | /** 6 | * Created by Oguz Sahin on 1/5/2022. 7 | */ 8 | open class BaseUiState { 9 | fun getViewVisibility(isVisible: Boolean) = if (isVisible) View.VISIBLE else View.GONE 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/common/BindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.common 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import com.bumptech.glide.Glide 6 | 7 | /** 8 | * Created by Oguz Sahin on 11/11/2021. 9 | */ 10 | 11 | @BindingAdapter("imageUrl") 12 | fun ImageView.loadImage(url: String?) { 13 | if (url.isNullOrEmpty()) return 14 | Glide.with(this).load(url).into(this) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/common/FooterAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.common 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil.inflate 6 | import androidx.paging.LoadState 7 | import androidx.paging.LoadStateAdapter 8 | import com.huawei.pagingexampleproject.R 9 | import com.huawei.pagingexampleproject.databinding.ItemPagingFooterBinding 10 | 11 | /** 12 | * Created by Oguz Sahin on 11/11/2021. 13 | */ 14 | 15 | class FooterAdapter( 16 | private val retry: () -> Unit 17 | ) : LoadStateAdapter() { 18 | override fun onBindViewHolder(holder: FooterViewHolder, loadState: LoadState) { 19 | holder.bind(loadState) 20 | } 21 | 22 | override fun onCreateViewHolder( 23 | parent: ViewGroup, 24 | loadState: LoadState 25 | ): FooterViewHolder { 26 | val itemPagingFooterBinding = inflate( 27 | LayoutInflater.from(parent.context), 28 | R.layout.item_paging_footer, 29 | parent, 30 | false 31 | ) 32 | return FooterViewHolder(itemPagingFooterBinding, retry) 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/common/FooterUiState.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.common 2 | 3 | import android.content.Context 4 | import androidx.paging.LoadState 5 | import com.huawei.pagingexampleproject.R 6 | 7 | /** 8 | * Created by Oguz Sahin on 11/11/2021. 9 | */ 10 | data class FooterUiState(private val loadState: LoadState) : BaseUiState() { 11 | 12 | fun getLoadingVisibility() = getViewVisibility(isVisible = loadState is LoadState.Loading) 13 | 14 | fun getErrorVisibility() = getViewVisibility(isVisible = loadState is LoadState.Error) 15 | 16 | fun getErrorMessage(context: Context) = if (loadState is LoadState.Error) { 17 | loadState.error.localizedMessage ?: context.getString(R.string.something_went_wrong) 18 | } else "" 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/common/FooterViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.common 2 | 3 | import androidx.paging.LoadState 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.huawei.pagingexampleproject.databinding.ItemPagingFooterBinding 6 | import com.huawei.pagingexampleproject.util.ext.executeWithAction 7 | 8 | /** 9 | * Created by Oguz Sahin on 11/11/2021. 10 | */ 11 | class FooterViewHolder( 12 | private val binding: ItemPagingFooterBinding, 13 | retry: () -> Unit 14 | ) : RecyclerView.ViewHolder(binding.root) { 15 | 16 | init { 17 | binding.btnRetry.setOnClickListener { retry.invoke() } 18 | } 19 | 20 | fun bind(loadState: LoadState) { 21 | binding.executeWithAction { 22 | footerUiState = FooterUiState(loadState) 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/model/NameModel.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.model 2 | 3 | data class NameModel( 4 | val title: String, 5 | val first: String, 6 | val last: String 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/model/PictureModel.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.model 2 | 3 | /** 4 | * Created by Oguz Sahin on 11/10/2021. 5 | */ 6 | data class PictureModel( 7 | val thumbnail: String 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/model/UserModel.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.model 2 | 3 | /** 4 | * Created by Oguz Sahin on 11/10/2021. 5 | */ 6 | data class UserModel( 7 | val name: NameModel, 8 | val email: String, 9 | val phone: String, 10 | val picture: PictureModel 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/model/UserResponse.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.model 2 | 3 | /** 4 | * Created by Oguz Sahin on 11/10/2021. 5 | */ 6 | data class UserResponse( 7 | val results: ArrayList 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/pagingdatasource/UserPagingDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.pagingdatasource 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.paging.PagingState 5 | import com.huawei.pagingexampleproject.data.model.UserModel 6 | import com.huawei.pagingexampleproject.network.UserService 7 | 8 | /** 9 | * Created by Oguz Sahin on 11/10/2021. 10 | */ 11 | 12 | 13 | class UserPagingDataSource(private val userService: UserService) : 14 | PagingSource() { 15 | 16 | override suspend fun load(params: LoadParams): LoadResult { 17 | val page = params.key ?: STARTING_PAGE_INDEX 18 | return try { 19 | val response = userService.getUsers(page, params.loadSize) 20 | LoadResult.Page( 21 | data = response.results, 22 | prevKey = if (page == STARTING_PAGE_INDEX) null else page.minus(1), 23 | nextKey = if (response.results.isEmpty()) null else page.plus(1) 24 | ) 25 | } catch (exception: Exception) { 26 | return LoadResult.Error(exception) 27 | } 28 | } 29 | 30 | 31 | override fun getRefreshKey(state: PagingState): Int? { 32 | return state.anchorPosition?.let { anchorPosition -> 33 | state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) 34 | ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) 35 | } 36 | } 37 | 38 | companion object { 39 | private const val STARTING_PAGE_INDEX = 1 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.repository 2 | 3 | import androidx.paging.PagingData 4 | import com.huawei.pagingexampleproject.data.model.UserModel 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by Oguz Sahin on 11/10/2021. 9 | */ 10 | 11 | interface UserRepository { 12 | fun getUsers(): Flow> 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/data/repository/UserRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.data.repository 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import com.huawei.pagingexampleproject.data.model.UserModel 7 | import com.huawei.pagingexampleproject.data.pagingdatasource.UserPagingDataSource 8 | import com.huawei.pagingexampleproject.network.UserService 9 | import kotlinx.coroutines.flow.Flow 10 | import javax.inject.Inject 11 | import javax.inject.Singleton 12 | 13 | /** 14 | * Created by Oguz Sahin on 11/10/2021. 15 | */ 16 | 17 | @Singleton 18 | class UserRepositoryImpl @Inject constructor( 19 | private val userService: UserService 20 | ) : UserRepository { 21 | override fun getUsers(): Flow> { 22 | return Pager( 23 | config = PagingConfig( 24 | pageSize = NETWORK_PAGE_SIZE 25 | ), 26 | pagingSourceFactory = { UserPagingDataSource(userService) } 27 | ).flow 28 | } 29 | 30 | 31 | companion object { 32 | const val NETWORK_PAGE_SIZE = 20 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.di 2 | 3 | import android.content.Context 4 | import com.huawei.pagingexampleproject.BuildConfig 5 | import com.huawei.pagingexampleproject.network.UserService 6 | import com.huawei.pagingexampleproject.util.NetworkConnectionInterceptor 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import okhttp3.OkHttpClient 13 | import okhttp3.logging.HttpLoggingInterceptor 14 | import retrofit2.Retrofit 15 | import retrofit2.converter.gson.GsonConverterFactory 16 | import java.util.concurrent.TimeUnit.SECONDS 17 | import javax.inject.Singleton 18 | 19 | /** 20 | * Created by Oguz Sahin on 11/10/2021. 21 | */ 22 | 23 | @Module 24 | @InstallIn(SingletonComponent::class) 25 | object NetworkModule { 26 | 27 | private const val CONNECT_TIMEOUT = 20L 28 | private const val READ_TIMEOUT = 60L 29 | private const val WRITE_TIMEOUT = 120L 30 | 31 | 32 | @Provides 33 | @Singleton 34 | fun provideOkHttpClient(@ApplicationContext context: Context): OkHttpClient { 35 | val httpLoggingInterceptor = HttpLoggingInterceptor().apply { 36 | level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY 37 | else HttpLoggingInterceptor.Level.NONE 38 | } 39 | return OkHttpClient.Builder() 40 | .addInterceptor(httpLoggingInterceptor) 41 | .addInterceptor(NetworkConnectionInterceptor(context)) 42 | .connectTimeout(CONNECT_TIMEOUT, SECONDS) 43 | .readTimeout(READ_TIMEOUT, SECONDS) 44 | .writeTimeout(WRITE_TIMEOUT, SECONDS) 45 | .build() 46 | } 47 | 48 | @Provides 49 | @Singleton 50 | fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { 51 | return Retrofit.Builder() 52 | .baseUrl(BuildConfig.API_URL) 53 | .addConverterFactory(GsonConverterFactory.create()) 54 | .client(okHttpClient) 55 | .build() 56 | } 57 | 58 | 59 | @Provides 60 | @Singleton 61 | fun provideUserService(retrofit: Retrofit): UserService { 62 | return retrofit.create(UserService::class.java) 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.di 2 | 3 | import com.huawei.pagingexampleproject.data.repository.UserRepository 4 | import com.huawei.pagingexampleproject.data.repository.UserRepositoryImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | /** 11 | * Created by Oguz Sahin on 11/10/2021. 12 | */ 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | abstract class RepositoryModule { 17 | 18 | @Binds 19 | abstract fun provideUserRepository(userRepository: UserRepositoryImpl): UserRepository 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/network/UserService.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.network 2 | 3 | import com.huawei.pagingexampleproject.data.model.UserResponse 4 | import retrofit2.http.GET 5 | import retrofit2.http.Query 6 | 7 | /** 8 | * Created by Oguz Sahin on 11/10/2021. 9 | */ 10 | interface UserService { 11 | 12 | @GET(".") 13 | suspend fun getUsers( 14 | @Query("page") page: Int, 15 | @Query("results") results: Int, 16 | ): UserResponse 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/ui/ItemUserUiState.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.ui 2 | 3 | import com.huawei.pagingexampleproject.common.BaseUiState 4 | import com.huawei.pagingexampleproject.data.model.UserModel 5 | 6 | /** 7 | * Created by Oguz Sahin on 11/11/2021. 8 | */ 9 | data class UserItemUiState(private val userModel: UserModel) : BaseUiState() { 10 | 11 | fun getImageUrl() = userModel.picture.thumbnail 12 | 13 | fun getName() = "${userModel.name.first} ${userModel.name.last}" 14 | 15 | fun getPhone() = userModel.phone 16 | 17 | fun getMail() = userModel.email 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/ui/UserActivity.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.viewModels 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.paging.LoadState 8 | import androidx.paging.PagingData 9 | import com.huawei.pagingexampleproject.R 10 | import com.huawei.pagingexampleproject.common.FooterAdapter 11 | import com.huawei.pagingexampleproject.databinding.ActivityUserBinding 12 | import com.huawei.pagingexampleproject.util.ext.collect 13 | import com.huawei.pagingexampleproject.util.ext.collectLast 14 | import com.huawei.pagingexampleproject.util.ext.executeWithAction 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import kotlinx.coroutines.flow.distinctUntilChangedBy 17 | import kotlinx.coroutines.flow.map 18 | import javax.inject.Inject 19 | 20 | @AndroidEntryPoint 21 | class UserActivity : AppCompatActivity() { 22 | private lateinit var binding: ActivityUserBinding 23 | private val viewModel: UserViewModel by viewModels() 24 | 25 | @Inject 26 | lateinit var userAdapter: UsersAdapter 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | setBinding() 31 | setListener() 32 | setAdapter() 33 | collectLast(viewModel.userItemsUiStates, ::setUsers) 34 | } 35 | 36 | private fun setBinding() { 37 | binding = DataBindingUtil.setContentView(this, R.layout.activity_user) 38 | } 39 | 40 | private fun setListener() { 41 | binding.btnRetry.setOnClickListener { userAdapter.retry() } 42 | } 43 | 44 | 45 | private fun setAdapter() { 46 | collect(flow = userAdapter.loadStateFlow 47 | .distinctUntilChangedBy { it.source.refresh } 48 | .map { it.refresh }, 49 | action = ::setUsersUiState 50 | ) 51 | binding.rvUsers.adapter = userAdapter.withLoadStateFooter(FooterAdapter(userAdapter::retry)) 52 | } 53 | 54 | private fun setUsersUiState(loadState: LoadState) { 55 | binding.executeWithAction { 56 | usersUiState = UsersUiState(loadState) 57 | } 58 | } 59 | 60 | private suspend fun setUsers(userItemsPagingData: PagingData) { 61 | userAdapter.submitData(userItemsPagingData) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/ui/UserViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.ui 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import com.huawei.pagingexampleproject.databinding.ItemUserBinding 5 | import com.huawei.pagingexampleproject.util.ext.executeWithAction 6 | 7 | /** 8 | * Created by Oguz Sahin on 11/11/2021. 9 | */ 10 | class UserViewHolder(private val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root) { 11 | fun bind(userItemUiState: UserItemUiState) { 12 | binding.executeWithAction { 13 | this.userItemUiState = userItemUiState 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/ui/UserViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import androidx.paging.cachedIn 6 | import androidx.paging.map 7 | import com.huawei.pagingexampleproject.data.repository.UserRepository 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.map 10 | import javax.inject.Inject 11 | 12 | /** 13 | * Created by Oguz Sahin on 11/10/2021. 14 | */ 15 | @HiltViewModel 16 | class UserViewModel @Inject constructor(userRepository: UserRepository) : ViewModel() { 17 | val userItemsUiStates = userRepository.getUsers() 18 | .map { pagingData -> 19 | pagingData.map { userModel -> UserItemUiState(userModel) } 20 | }.cachedIn(viewModelScope) 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/ui/UsersAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.ui 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil.inflate 6 | import androidx.paging.PagingDataAdapter 7 | import androidx.recyclerview.widget.DiffUtil 8 | import com.huawei.pagingexampleproject.R 9 | import com.huawei.pagingexampleproject.databinding.ItemUserBinding 10 | import javax.inject.Inject 11 | 12 | /** 13 | * Created by Oguz Sahin on 11/10/2021. 14 | */ 15 | class UsersAdapter @Inject constructor() : 16 | PagingDataAdapter(Comparator) { 17 | 18 | override fun onBindViewHolder(holder: UserViewHolder, position: Int) { 19 | getItem(position)?.let { userItemUiState -> holder.bind(userItemUiState) } 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder { 23 | 24 | val binding = inflate( 25 | LayoutInflater.from(parent.context), 26 | R.layout.item_user, 27 | parent, 28 | false 29 | ) 30 | 31 | return UserViewHolder(binding) 32 | } 33 | 34 | object Comparator : DiffUtil.ItemCallback() { 35 | override fun areItemsTheSame(oldItem: UserItemUiState, newItem: UserItemUiState): Boolean { 36 | return oldItem.getPhone() == newItem.getPhone() 37 | } 38 | 39 | override fun areContentsTheSame( 40 | oldItem: UserItemUiState, 41 | newItem: UserItemUiState 42 | ): Boolean { 43 | return oldItem == newItem 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/ui/UsersUiState.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.ui 2 | 3 | import android.content.Context 4 | import androidx.paging.LoadState 5 | import com.huawei.pagingexampleproject.R 6 | import com.huawei.pagingexampleproject.common.BaseUiState 7 | 8 | /** 9 | * Created by Oguz Sahin on 11/11/2021. 10 | */ 11 | data class UsersUiState( 12 | private val loadState: LoadState 13 | ) : BaseUiState() { 14 | 15 | fun getProgressBarVisibility() = getViewVisibility(isVisible = loadState is LoadState.Loading) 16 | 17 | fun getListVisibility() = getViewVisibility(isVisible = loadState is LoadState.NotLoading) 18 | 19 | fun getErrorVisibility() = getViewVisibility(isVisible = loadState is LoadState.Error) 20 | 21 | fun getErrorMessage(context: Context) = if (loadState is LoadState.Error) { 22 | loadState.error.localizedMessage ?: context.getString(R.string.something_went_wrong) 23 | } else "" 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/util/NetworkConnectionInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.util 2 | 3 | import android.content.Context 4 | import com.huawei.pagingexampleproject.util.NetworkUtils.isNetworkAvailable 5 | import okhttp3.Interceptor 6 | import okhttp3.Request 7 | import okhttp3.Response 8 | import java.io.IOException 9 | 10 | /** 11 | * Created by Oguz Sahin on 11/10/2021. 12 | */ 13 | 14 | class NetworkConnectionInterceptor(private val context: Context) : Interceptor { 15 | @Throws(IOException::class) 16 | override fun intercept(chain: Interceptor.Chain): Response { 17 | if (!isNetworkAvailable(context)) { 18 | throw NoConnectionException() 19 | } 20 | val builder: Request.Builder = chain.request().newBuilder() 21 | return chain.proceed(builder.build()) 22 | } 23 | 24 | inner class NoConnectionException : IOException() { 25 | override val message: String 26 | get() = super.message ?: "No Internet Connection" 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/util/NetworkUtils.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.util 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | import android.os.Build 7 | 8 | /** 9 | * Created by Oguz Sahin on 11/10/2021. 10 | */ 11 | 12 | object NetworkUtils { 13 | @Suppress("DEPRECATION") 14 | fun isNetworkAvailable(context: Context): Boolean { 15 | val connectivityManager = 16 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 17 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 18 | val capabilities = 19 | connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) 20 | ?: return false 21 | when { 22 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true 23 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true 24 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true 25 | else -> false 26 | } 27 | } else { 28 | connectivityManager.activeNetworkInfo?.isConnected ?: false 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/util/ext/LifecycleOwnerExt.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.util.ext 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.lifecycleScope 6 | import androidx.lifecycle.repeatOnLifecycle 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.collect 9 | import kotlinx.coroutines.flow.collectLatest 10 | import kotlinx.coroutines.launch 11 | 12 | /** 13 | * Created by Oguz Sahin on 12/31/2021. 14 | */ 15 | 16 | 17 | fun LifecycleOwner.collectLast(flow: Flow, action: suspend (T) -> Unit) { 18 | lifecycleScope.launch { 19 | repeatOnLifecycle(Lifecycle.State.STARTED) { 20 | flow.collectLatest(action) 21 | } 22 | } 23 | } 24 | 25 | 26 | fun LifecycleOwner.collect(flow: Flow, action: suspend (T) -> Unit) { 27 | lifecycleScope.launch { 28 | repeatOnLifecycle(Lifecycle.State.STARTED) { 29 | flow.collect { 30 | action.invoke(it) 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/huawei/pagingexampleproject/util/ext/ViewDataBindingExt.kt: -------------------------------------------------------------------------------- 1 | package com.huawei.pagingexampleproject.util.ext 2 | 3 | import androidx.databinding.ViewDataBinding 4 | 5 | /** 6 | * Created by Oguz Sahin on 1/5/2022. 7 | */ 8 | 9 | 10 | fun T.executeWithAction(action: T.() -> Unit) { 11 | action() 12 | executePendingBindings() 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 18 | 19 | 28 | 29 | 40 | 41 | 50 | 51 | 52 |