├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── klaster-test ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── rongi │ │ └── klaster │ │ ├── Article.kt │ │ ├── MyViewHolder.kt │ │ ├── TestKlasterBuilder.kt │ │ ├── TestKlasterBuilderWithViewHolder.kt │ │ └── TestUtils.kt │ └── main │ ├── AndroidManifest.xml │ └── res │ └── layout │ └── list_item.xml ├── klaster ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── github │ └── rongi │ └── klaster │ ├── Klaster.kt │ ├── KlasterAdapter.kt │ ├── KlasterBuilder.kt │ ├── KlasterBuilderWithViewHolder.kt │ └── KlasterViewHolder.kt ├── sample-app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── rongi │ │ └── klaster │ │ └── samples │ │ ├── common │ │ ├── DividerItemDecoration.kt │ │ └── Utils.kt │ │ ├── examples │ │ ├── customviewholder │ │ │ ├── CustomViewHolderExampleActivity.kt │ │ │ ├── CustomViewHolderExamplePresenter.kt │ │ │ ├── CustomViewHolderExampleView.kt │ │ │ └── MyViewHolder.kt │ │ ├── extensions │ │ │ └── ExtensionsExample.kt │ │ ├── functional │ │ │ ├── FunctionalExampleActivity.kt │ │ │ ├── FunctionalExamplePresenter.kt │ │ │ └── FunctionalExampleView.kt │ │ ├── multipleviewtypes │ │ │ ├── MultipleViewTypesExampleActivity.kt │ │ │ ├── MultipleViewTypesExamplePresenter.kt │ │ │ └── MultipleViewTypesExampleView.kt │ │ ├── simple │ │ │ ├── SimpleExampleActivity.kt │ │ │ ├── SimpleExamplePresenter.kt │ │ │ └── SimpleExampleView.kt │ │ ├── subclassing │ │ │ └── ArticlesAdapter.kt │ │ └── verbose │ │ │ └── VerboseExample.kt │ │ └── main │ │ ├── MainActivity.kt │ │ ├── data │ │ └── Articles.kt │ │ └── model │ │ └── Article.kt │ └── res │ ├── drawable │ ├── delete.xml │ └── divider.xml │ ├── layout │ ├── header.xml │ ├── list_item.xml │ ├── list_item1.xml │ ├── list_item2.xml │ └── recycler_view_activity.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | projectFilesBackup* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dmitry Ryadnenko 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 | [![](https://jitpack.io/v/rongi/klaster.svg)](https://jitpack.io/#rongi/klaster) 2 | 3 | # Declare RecyclerView adapters without boilerplate 4 | 5 | With this library: 6 | 7 | ```kotlin 8 | private fun articlesAdapter() = Klaster.get() 9 | .itemCount { articles.size } 10 | .view(R.layout.list_item, layoutInflater) 11 | .bind { position -> 12 | val article = articles[position] 13 | item_text.text = article.title 14 | itemView.onClick = { presenter.onArticleClick(article) } 15 | } 16 | .build() 17 | ``` 18 | 19 | The same adapter declared by subclassing: 20 | 21 | ```java 22 | private class ArticlesAdapter( 23 | private val layoutInflater: LayoutInflater 24 | ) : RecyclerView.Adapter() { 25 | 26 | val onItemClick: (() -> Unit)? = null 27 | 28 | override fun getItemCount(): Int { 29 | return articles.size 30 | } 31 | 32 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticlesViewHolder { 33 | val view = layoutInflater.inflate(R.layout.list_item, parent, false) 34 | return ArticlesViewHolder(view) 35 | } 36 | 37 | override fun onBindViewHolder(holder: ArticlesViewHolder, position: Int) { 38 | val article = articles[position] 39 | holder.articleTitle.text = article.title 40 | holder.itemView.onClick = { onItemClick?.invoke() } 41 | } 42 | 43 | private class ArticlesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 44 | val articleTitle: TextView = itemView.findViewById(R.id.item_text) 45 | } 46 | 47 | } 48 | ``` 49 | 50 | Ever wondered why you need to declare an extra class for each of your adapters when essentially an adapter is just two functions combined together: `onCreateViewHolder()` and `onBindViewHolder()`? Why can't we have something that takes these two functions and construct a proper adapter for us? Well, you can with this library. And with the power of Kotlin Android Extensions, you don't even need to create `ViewHolder` classes anymore. 51 | 52 | This library doesn't compromise on flexibility and doesn't hide stuff from you. If it's possible to do something by declaring a new adapter class, it's possible to do it with this library also. It's just a more concise way to declare RecyclerView adapters. 53 | 54 | If you ever feel like this adapter builder is too verbose, well, with Kotlin extension functions you can tailor it for your needs. Or you can build more complex things on top of it. Again, just with extension functions. 55 | 56 | Usage 57 | ===== 58 | 59 | ## Basic 60 | 61 | ```kotlin 62 | private fun createAdapter() = Klaster.get() 63 | .itemCount { articles.size } 64 | .view(R.layout.list_item, layoutInflater) 65 | .bind { position -> 66 | val article = articles[position] 67 | item_text.text = article.title 68 | itemView.onClick = { presenter.onArticleClick(article) } 69 | } 70 | .build() 71 | ``` 72 | 73 | This is how it can look like inside an `Activity` implemented with MVP. 74 | 75 | ```kotlin 76 | class SimpleExampleActivity : AppCompatActivity(), SimpleExampleView { 77 | 78 | private lateinit var adapter: RecyclerView.Adapter<*> 79 | 80 | private lateinit var presenter: SimpleExamplePresenter 81 | 82 | private var articles: List
= emptyList() 83 | 84 | override fun onCreate(...) { 85 | super.onCreate(savedInstanceState) 86 | setContentView(R.layout.recycler_view_activity) 87 | recycler_view.init(this) 88 | 89 | adapter = createAdapter() 90 | 91 | recycler_view.adapter = adapter 92 | 93 | presenter = SimpleExamplePresenter(view = this) 94 | presenter.onViewCreated() 95 | } 96 | 97 | override fun showArticles(articles: List
) { 98 | this.articles = articles 99 | adapter.notifyDataSetChanged() 100 | } 101 | 102 | private fun createAdapter() = Klaster.get() 103 | .itemCount { articles.size } 104 | .view(R.layout.list_item, layoutInflater) 105 | .bind { position -> 106 | val article = articles[position] 107 | item_text.text = article.title 108 | itemView.onClick = { presenter.onArticleClick(article) } 109 | } 110 | .build() 111 | 112 | } 113 | ``` 114 | 115 | ## Multiple view types 116 | 117 | ```kotlin 118 | class MultipleViewTypesExampleActivity : AppCompatActivity(), MultipleViewTypesExampleView { 119 | // ... 120 | 121 | private fun createAdapter() = Klaster.get() 122 | .itemCount { listItems.size } 123 | .getItemViewType { position -> 124 | when (listItems[position]) { 125 | is ArticleViewData -> 0 126 | is HeaderViewData -> 1 127 | } 128 | } 129 | .view { viewType, parent -> 130 | when (viewType) { 131 | 0 -> layoutInflater.inflate(R.layout.list_item, parent, false) 132 | 1 -> layoutInflater.inflate(R.layout.header, parent, false) 133 | else -> throw IllegalStateException("Unknown view type: $viewType") 134 | } 135 | } 136 | .bind { position -> 137 | val listItem = listItems[position] 138 | 139 | when (listItem) { 140 | is ArticleViewData -> { 141 | item_text.text = listItem.article.title 142 | itemView.onClick = { presenter.onArticleClick(listItem.article) } 143 | } 144 | is HeaderViewData -> { 145 | header_text.text = listItem.headerText 146 | } 147 | } 148 | } 149 | .build() 150 | 151 | private var listItems: List = emptyList() 152 | 153 | override fun showListItems(listItems: List) { 154 | this.listItems = listItems 155 | adapter.notifyDataSetChanged() 156 | } 157 | 158 | sealed class ListItemViewData 159 | 160 | data class HeaderViewData( 161 | val headerText: String 162 | ): ListItemViewData() 163 | 164 | data class ArticleViewData( 165 | val article: Article 166 | ): ListItemViewData() 167 | 168 | } 169 | ``` 170 | 171 | Full example is a part of library's sample app and can be found [here](https://github.com/rongi/klaster/tree/master/sample-app/src/main/java/com/github/rongi/klaster/samples/examples/multipleviewtypes). 172 | 173 | ## With a custom `ViewHolder` 174 | 175 | ```kotlin 176 | private fun createAdapter() = Klaster.withViewHolder() 177 | .itemCount { articles.size } 178 | .viewHolder { _, parent -> 179 | val view = layoutInflater.inflate(R.layout.list_item, parent, false) 180 | MyViewHolder(view) 181 | } 182 | .bind { position -> 183 | val article = articles[position] 184 | articleTitle.text = article.title 185 | itemView.onClick = { presenter.onArticleClick(article) } 186 | } 187 | .build() 188 | ``` 189 | 190 | ## But what if I need to overload more functions? 191 | 192 | With this builder, you can "overload" any function you want that can be overloaded by traditional subclassing of `RecyclerView.Adapter`. 193 | 194 | ```kotlin 195 | fun createAdapter(layoutInflater: LayoutInflater) = Klaster.get() 196 | .itemCount { articles.size } 197 | .getItemViewType { position -> position % 2 } 198 | .view { viewType, parent -> 199 | when (viewType) { 200 | ITEM_TYPE_1 -> layoutInflater.inflate(R.layout.list_item1, parent, false) 201 | ITEM_TYPE_2 -> layoutInflater.inflate(R.layout.list_item2, parent, false) 202 | else -> throw IllegalStateException("Unknown type: $viewType") 203 | } 204 | } 205 | .bind { position -> 206 | val article = articles[position] 207 | item_text.text = article.title 208 | } 209 | .bind { position, payloads -> } 210 | .getItemId { } 211 | .setHasStableIds { } 212 | .onAttachedToRecyclerView { } 213 | .onDetachedFromRecyclerView { } 214 | .registerAdapterDataObserver { } 215 | .unregisterAdapterDataObserver { } 216 | .onFailedToRecycleView { } 217 | .onViewAttachedToWindow { } 218 | .onViewDetachedFromWindow { } 219 | .onViewRecycled { } 220 | .build() 221 | ``` 222 | 223 | ## Functional way to create adapters 224 | 225 | But does list of items really belong to the `Activity`? Can I achieve better separation of concerns using this library? Yes, and here is an example of how it can be done in a clean and beautiful functional way without subclassing. 226 | 227 | The function defined below, `createAdapter()`, creates an adapter backed by a simple `List` of items. This function returns two things: 228 | 229 | 1. A `RecyclerView.Adapter`, which you can give to your `RecyclerView`. 230 | 2. A `ListViewPresenter` interface. This interface you can use to update contents of your adapter, it has a single method that replaces all the items in the adapter with the new ones. You can even pass this presenter into your main presenter as a dependency. 231 | 232 | ```kotlin 233 | private fun createAdapter( 234 | layoutInflater: LayoutInflater, 235 | onItemClick: (Article) -> Unit 236 | ): Pair, ListViewPresenter> { 237 | var articles: List
= emptyList() 238 | 239 | val adapter = Klaster.get() 240 | .itemCount { articles.size } 241 | .view(R.layout.list_item, layoutInflater) 242 | .bind { position -> 243 | val article = articles[position] 244 | item_text.text = article.title 245 | itemView.onClick = { onItemClick(article) } 246 | } 247 | .build() 248 | 249 | val listViewPresenter = object : ListViewPresenter { 250 | override fun setItems(items: List
) { 251 | articles = items 252 | adapter.notifyDataSetChanged() 253 | } 254 | } 255 | 256 | return adapter to listViewPresenter 257 | } 258 | 259 | interface ListViewPresenter { 260 | fun setItems(items: List
) 261 | } 262 | ``` 263 | 264 | Why is this preferred over inheritance? Because it's simpler (less interweaved) and you can achieve a better separation of concerns this way. For example `ListViewPresenter` implementation can be extracted from this function and reused for all other cases where the adapter is backed by a `List`. 265 | 266 | ## Create your own extensions 267 | 268 | You can tailor the builder for your needs by creating your own, even more elegant APIs using Kotlin extension functions. For example, if you want to create an adapter for a `List` of items that never change, then you may want to have a builder that can do things like this (notice no `itemCount()` function). 269 | 270 | ```kotlin 271 | fun createAdapter(articles: List
, layoutInflater: LayoutInflater) = Klaster.get() 272 | .view(R.layout.list_item, layoutInflater) 273 | .bind(articles) { article, position -> 274 | item_text.text = article.title 275 | } 276 | .build() 277 | ``` 278 | 279 | You can achieve it with this extension function. 280 | 281 | ```kotlin 282 | fun KlasterBuilder.bind(items: List, binder: KlasterViewHolder.(item: T, position: Int) -> Unit): KlasterBuilder = 283 | this.itemCount(items.size) 284 | .bind { position -> 285 | val item = items[position] 286 | binder(item, position) 287 | } 288 | ``` 289 | 290 | What if you want your adapter to get items from a list that can change? You may want to have a builder that can do this then. 291 | 292 | ```kotlin 293 | fun createAdapter(articles: () -> List
, layoutInflater: LayoutInflater) = Klaster.get() 294 | .view(R.layout.list_item, layoutInflater) 295 | .bind(articles) { article, position -> 296 | item_text.text = article.title 297 | } 298 | .build() 299 | ``` 300 | 301 | You can get that with this extension function. 302 | 303 | ```kotlin 304 | fun KlasterBuilder.bind(items: () -> List, binder: KlasterViewHolder.(item: T, position: Int) -> Unit): KlasterBuilder = 305 | this.itemCount { items().size } 306 | .bind { position -> 307 | val item = items()[position] 308 | binder(item, position) 309 | } 310 | ``` 311 | 312 | Download 313 | ======== 314 | 315 | ```groovy 316 | allprojects { 317 | repositories { 318 | ... 319 | maven { url 'https://jitpack.io' } 320 | } 321 | } 322 | ``` 323 | 324 | ```groovy 325 | dependencies { 326 | implementation 'com.github.rongi:klaster:0.3.5' 327 | } 328 | ``` 329 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.71' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.2.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | jcenter() 21 | google() 22 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /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/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Sep 27 23:29:06 CEST 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.7-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 | -------------------------------------------------------------------------------- /klaster-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /klaster-test/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 27 7 | 8 | defaultConfig { 9 | minSdkVersion 23 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | } 24 | 25 | dependencies { 26 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 27 | implementation 'com.android.support:appcompat-v7:27.1.1' 28 | implementation 'com.android.support:recyclerview-v7:27.1.1' 29 | 30 | implementation project(":klaster") 31 | 32 | androidTestImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0" 33 | androidTestImplementation "org.mockito:mockito-android:2.27.0" 34 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { 35 | exclude group: 'com.android.support', module: 'support-annotations' 36 | }) 37 | } 38 | 39 | repositories { 40 | mavenCentral() 41 | } 42 | 43 | androidExtensions { 44 | experimental = true 45 | } 46 | -------------------------------------------------------------------------------- /klaster-test/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 | -------------------------------------------------------------------------------- /klaster-test/src/androidTest/java/com/github/rongi/klaster/Article.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | data class Article( 4 | val title: String 5 | ) -------------------------------------------------------------------------------- /klaster-test/src/androidTest/java/com/github/rongi/klaster/MyViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.widget.TextView 6 | import com.github.rongi.klaster.test.test.R 7 | 8 | class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 9 | val articleTitle: TextView = itemView.findViewById(R.id.item_text) 10 | } 11 | -------------------------------------------------------------------------------- /klaster-test/src/androidTest/java/com/github/rongi/klaster/TestKlasterBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.FrameLayout 10 | import android.widget.TextView 11 | import com.github.rongi.klaster.test.test.R 12 | import com.nhaarman.mockitokotlin2.* 13 | import kotlinx.android.synthetic.main.list_item.* 14 | import kotlinx.android.synthetic.main.list_item.view.* 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | import org.mockito.ArgumentMatchers 18 | 19 | @RunWith(AndroidJUnit4::class) 20 | class TestKlasterBuilder { 21 | 22 | private val appContext = InstrumentationRegistry.getTargetContext() 23 | 24 | private val parent = FrameLayout(appContext) 25 | 26 | private val layoutInflater = LayoutInflater.from(appContext) 27 | 28 | private val viewMock: View = mock() 29 | 30 | private val viewHolderMock = KlasterViewHolder(viewMock) 31 | 32 | private val recyclerViewMock: RecyclerView = mock() 33 | 34 | private val adapterDataObserverMock: RecyclerView.AdapterDataObserver = mock() 35 | 36 | @Test 37 | fun createsViewFromResourceId() { 38 | val items = listOf(Article("article title")) 39 | 40 | val adapter = Klaster.get() 41 | .itemCount { items.size } 42 | .view(R.layout.list_item, layoutInflater) 43 | .bind { position -> 44 | item_text.text = items[position].title 45 | } 46 | .build() 47 | 48 | val viewHolder = adapter.createViewHolder(parent, 0) 49 | 50 | viewHolder.itemView.item_text assertIs TextView::class.java 51 | } 52 | 53 | @Test 54 | fun createsViewFromFunction() { 55 | val items = listOf(Article("article title")) 56 | 57 | val adapter = Klaster.get() 58 | .itemCount { items.size } 59 | .viewBy { 60 | TextView(appContext) 61 | } 62 | .bind { position -> 63 | (itemView as TextView).text = items[position].title 64 | } 65 | .build() 66 | 67 | val viewHolder = adapter.createViewHolder(parent, 0).apply { 68 | adapter.bindViewHolder(this, 0) 69 | } 70 | 71 | viewHolder.itemView assertIs TextView::class.java 72 | } 73 | 74 | @Test 75 | fun createsViewFromResourceIdWithInitFunction() { 76 | val items = listOf(Article("article title")) 77 | 78 | val adapter = Klaster.get() 79 | .itemCount { items.size } 80 | .view(R.layout.list_item, layoutInflater) { 81 | this.item_text.error = "error message" 82 | } 83 | .bind { position -> 84 | item_text.text = items[position].title 85 | } 86 | .build() 87 | 88 | val viewHolder = adapter.createViewHolder(parent, 0) 89 | 90 | viewHolder.itemView.item_text assertIs TextView::class.java 91 | (viewHolder.itemView.item_text as TextView).error assertEquals "error message" 92 | } 93 | 94 | @Test 95 | fun createsViewFromFunctionWithParent() { 96 | val items = listOf(Article("article title")) 97 | 98 | val adapter = Klaster.get() 99 | .itemCount { items.size } 100 | .view { parent: ViewGroup -> 101 | LayoutInflater.from(appContext).inflate(R.layout.list_item, parent, false) 102 | } 103 | .bind { position -> 104 | item_text.text = items[position].title 105 | } 106 | .build() 107 | 108 | val viewHolder = adapter.createViewHolder(parent, 0) 109 | 110 | viewHolder.itemView.layoutParams assertIs FrameLayout.LayoutParams::class.java 111 | } 112 | 113 | @Test 114 | fun bindsView() { 115 | val items = listOf(Article("article title")) 116 | 117 | val adapter = Klaster.get() 118 | .itemCount { items.size } 119 | .view(R.layout.list_item, layoutInflater) 120 | .bind { position -> 121 | item_text.text = items[position].title 122 | } 123 | .build() 124 | 125 | val viewHolder = adapter.createViewHolder(parent, 0).apply { 126 | adapter.bindViewHolder(this, 0) 127 | } 128 | 129 | (viewHolder.itemView.item_text as TextView).text assertEquals "article title" 130 | } 131 | 132 | @Test 133 | fun bindsViewWithPosition() { 134 | val items = listOf(Article("article")) 135 | 136 | val adapter = Klaster.get() 137 | .itemCount { items.size } 138 | .view(R.layout.list_item, layoutInflater) 139 | .bind { position -> 140 | item_text.text = "${items[position].title} ${position + 1}" 141 | } 142 | .build() 143 | 144 | val viewHolder = adapter.createViewHolder(parent, 0).apply { 145 | adapter.bindViewHolder(this, 0) 146 | } 147 | 148 | (viewHolder.itemView.item_text as TextView).text assertEquals "article 1" 149 | } 150 | 151 | @Test 152 | fun itemCountWorks() { 153 | val items = listOf(Article("article1 title"), Article("article2 title")) 154 | 155 | val adapter = Klaster.get() 156 | .itemCount { items.size } 157 | .view(R.layout.list_item, layoutInflater) 158 | .bind { position -> 159 | item_text.text = items[position].title 160 | } 161 | .build() 162 | 163 | adapter.itemCount assertEquals 2 164 | } 165 | 166 | @Test 167 | fun itemCountFromNumberWorks() { 168 | val adapter = Klaster.get() 169 | .itemCount(42) 170 | .view(R.layout.list_item, layoutInflater) 171 | .bind { position -> item_text.text = "$position" } 172 | .build() 173 | 174 | adapter.itemCount assertEquals 42 175 | } 176 | 177 | @Test 178 | fun getItemIdWorks() { 179 | val adapter = Klaster.get() 180 | .itemCount(42) 181 | .view(R.layout.list_item, layoutInflater) 182 | .bind { position -> item_text.text = "$position" } 183 | .getItemId { position -> position * 2L } 184 | .build() 185 | 186 | adapter.getItemId(10) assertEquals 20L 187 | } 188 | 189 | @Test 190 | fun bindWithPayloadsWorks() { 191 | val mockFunction = mock<((binder: KlasterViewHolder, position: Int, payloads: MutableList) -> Unit)>() 192 | val adapter = Klaster.get() 193 | .itemCount(100) 194 | .view(R.layout.list_item, layoutInflater) 195 | .bind { position -> item_text.text = "$position" } 196 | .bind { position, payloads -> 197 | mockFunction(this, position, payloads) 198 | } 199 | .build() 200 | 201 | adapter.onBindViewHolder(viewHolderMock, 42, listOf("a")) 202 | 203 | verify(mockFunction).invoke(any(), eq(42), eq(mutableListOf("a"))) 204 | } 205 | 206 | @Test 207 | fun getItemViewTypeWorks() { 208 | val mockFunction = mock<((Int) -> Int)>().apply { 209 | whenever(this.invoke(ArgumentMatchers.anyInt())).thenReturn(11) 210 | } 211 | val adapter = Klaster.get() 212 | .itemCount(100) 213 | .view(R.layout.list_item, layoutInflater) 214 | .bind { position -> item_text.text = "$position" } 215 | .getItemViewType { position -> 216 | mockFunction(position) 217 | } 218 | .build() 219 | 220 | adapter.getItemViewType(42) 221 | 222 | verify(mockFunction).invoke(42) 223 | } 224 | 225 | @Test 226 | fun setHasStableIdsWorks() { 227 | val mockFunction = mock<((Boolean) -> Unit)>() 228 | val adapter = Klaster.get() 229 | .itemCount(100) 230 | .view(R.layout.list_item, layoutInflater) 231 | .bind { position -> item_text.text = "$position" } 232 | .setHasStableIds { hasStableId -> 233 | mockFunction(hasStableId) 234 | } 235 | .build() 236 | 237 | adapter.setHasStableIds(true) 238 | 239 | verify(mockFunction).invoke(true) 240 | } 241 | 242 | @Test 243 | fun onAttachedToRecyclerViewWorks() { 244 | val mockFunction = mock<((recyclerView: RecyclerView) -> Unit)>() 245 | val adapter = Klaster.get() 246 | .itemCount(100) 247 | .view(R.layout.list_item, layoutInflater) 248 | .bind { position -> item_text.text = "$position" } 249 | .onAttachedToRecyclerView { recyclerView -> 250 | mockFunction(recyclerView) 251 | } 252 | .build() 253 | 254 | adapter.onAttachedToRecyclerView(recyclerViewMock) 255 | 256 | verify(mockFunction).invoke(recyclerViewMock) 257 | } 258 | 259 | @Test 260 | fun onDetachedFromRecyclerViewWorks() { 261 | val mockFunction = mock<((recyclerView: RecyclerView) -> Unit)>() 262 | val adapter = Klaster.get() 263 | .itemCount(100) 264 | .view(R.layout.list_item, layoutInflater) 265 | .bind { position -> item_text.text = "$position" } 266 | .onDetachedFromRecyclerView { recyclerView -> 267 | mockFunction(recyclerView) 268 | } 269 | .build() 270 | 271 | adapter.onDetachedFromRecyclerView(recyclerViewMock) 272 | 273 | verify(mockFunction).invoke(recyclerViewMock) 274 | } 275 | 276 | @Test 277 | fun onViewAttachedToWindowWorks() { 278 | val mockFunction = mock<((holder: KlasterViewHolder) -> Unit)>() 279 | val adapter = Klaster.get() 280 | .itemCount(100) 281 | .view(R.layout.list_item, layoutInflater) 282 | .bind { position -> item_text.text = "$position" } 283 | .onViewAttachedToWindow { holder -> 284 | mockFunction(holder) 285 | } 286 | .build() 287 | 288 | adapter.onViewAttachedToWindow(viewHolderMock) 289 | 290 | verify(mockFunction).invoke(viewHolderMock) 291 | } 292 | 293 | @Test 294 | fun onViewDetachedFromWindowWorks() { 295 | val mockFunction = mock<((holder: KlasterViewHolder) -> Unit)>() 296 | val adapter = Klaster.get() 297 | .itemCount(100) 298 | .view(R.layout.list_item, layoutInflater) 299 | .bind { position -> item_text.text = "$position" } 300 | .onViewDetachedFromWindow { holder -> 301 | mockFunction(holder) 302 | } 303 | .build() 304 | 305 | adapter.onViewDetachedFromWindow(viewHolderMock) 306 | 307 | verify(mockFunction).invoke(viewHolderMock) 308 | } 309 | 310 | @Test 311 | fun onFailedToRecycleViewWorks() { 312 | val mockFunction = mock<((holder: KlasterViewHolder) -> Boolean)>().apply { 313 | whenever(this.invoke(anyOrNull())).thenReturn(true) 314 | } 315 | val adapter = Klaster.get() 316 | .itemCount(100) 317 | .view(R.layout.list_item, layoutInflater) 318 | .bind { position -> item_text.text = "$position" } 319 | .onFailedToRecycleView { holder -> 320 | mockFunction(holder) 321 | } 322 | .build() 323 | 324 | adapter.onFailedToRecycleView(viewHolderMock) 325 | 326 | verify(mockFunction).invoke(viewHolderMock) 327 | } 328 | 329 | @Test 330 | fun onViewRecycledWorks() { 331 | val mockFunction = mock<((holder: KlasterViewHolder) -> Unit)>() 332 | val adapter = Klaster.get() 333 | .itemCount(100) 334 | .view(R.layout.list_item, layoutInflater) 335 | .bind { position -> item_text.text = "$position" } 336 | .onViewRecycled { holder -> 337 | mockFunction(holder) 338 | } 339 | .build() 340 | 341 | adapter.onViewRecycled(viewHolderMock) 342 | 343 | verify(mockFunction).invoke(viewHolderMock) 344 | } 345 | 346 | @Test 347 | fun registerAdapterDataObserverWorks() { 348 | val mockFunction = mock<((observer: RecyclerView.AdapterDataObserver) -> Unit)>() 349 | val adapter = Klaster.get() 350 | .itemCount(100) 351 | .view(R.layout.list_item, layoutInflater) 352 | .bind { position -> item_text.text = "$position" } 353 | .registerAdapterDataObserver { observer -> 354 | mockFunction(observer) 355 | } 356 | .build() 357 | 358 | adapter.registerAdapterDataObserver(adapterDataObserverMock) 359 | 360 | verify(mockFunction).invoke(adapterDataObserverMock) 361 | } 362 | 363 | @Test 364 | fun unregisterAdapterDataObserverWorks() { 365 | val mockFunction = mock<((observer: RecyclerView.AdapterDataObserver) -> Unit)>() 366 | val adapter = Klaster.get() 367 | .itemCount(100) 368 | .view(R.layout.list_item, layoutInflater) 369 | .bind { position -> item_text.text = "$position" } 370 | .unregisterAdapterDataObserver { observer -> 371 | mockFunction(observer) 372 | } 373 | .build() 374 | 375 | adapter.unregisterAdapterDataObserver(adapterDataObserverMock) 376 | 377 | verify(mockFunction).invoke(adapterDataObserverMock) 378 | } 379 | 380 | 381 | @Test 382 | fun createsViewWithViewType() { 383 | val items = listOf(Article("article title")) 384 | 385 | val adapter = Klaster.get() 386 | .itemCount { items.size } 387 | .view { viewType: Int, parent: ViewGroup -> 388 | when (viewType) { 389 | TYPE1 -> TextView(appContext).apply { 390 | text = "type 1" 391 | } 392 | TYPE2 -> TextView(appContext).apply { 393 | text = "type 2" 394 | } 395 | else -> throw IllegalStateException("Unknown view type $viewType") 396 | } 397 | } 398 | .bind { position -> 399 | item_text.text = items[position].title 400 | } 401 | .build() 402 | 403 | val viewHolder1 = adapter.createViewHolder(parent, TYPE1) 404 | val viewHolder2 = adapter.createViewHolder(parent, TYPE2) 405 | 406 | viewHolder1.itemView.cast().text assertEquals "type 1" 407 | viewHolder2.itemView.cast().text assertEquals "type 2" 408 | } 409 | 410 | } 411 | 412 | const val TYPE1 = 1 413 | const val TYPE2 = 2 414 | -------------------------------------------------------------------------------- /klaster-test/src/androidTest/java/com/github/rongi/klaster/TestKlasterBuilderWithViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.FrameLayout 10 | import android.widget.TextView 11 | import com.github.rongi.klaster.test.test.R 12 | import com.nhaarman.mockitokotlin2.* 13 | import org.junit.Test 14 | import org.junit.runner.RunWith 15 | import org.mockito.ArgumentMatchers 16 | 17 | @RunWith(AndroidJUnit4::class) 18 | class TestKlasterBuilderWithViewHolder { 19 | 20 | private val appContext = InstrumentationRegistry.getTargetContext() 21 | 22 | private val parent = FrameLayout(appContext) 23 | 24 | private val layoutInflater = LayoutInflater.from(appContext) 25 | 26 | private val recyclerViewMock: RecyclerView = mock() 27 | 28 | private val adapterDataObserverMock: RecyclerView.AdapterDataObserver = mock() 29 | 30 | @Test 31 | fun createsViewHolder() { 32 | val items = listOf(Article("article title")) 33 | 34 | val adapter = Klaster.withViewHolder() 35 | .itemCount { items.size } 36 | .viewHolder { _, parent -> 37 | val view = layoutInflater.inflate(R.layout.list_item, parent, false) 38 | MyViewHolder(view) 39 | } 40 | .bind { position -> 41 | articleTitle.text = items[position].title 42 | } 43 | .build() 44 | 45 | val viewHolder = adapter.createViewHolder(parent, 0) 46 | 47 | viewHolder assertIs MyViewHolder::class.java 48 | } 49 | 50 | @Test 51 | fun createsViewHolderWithViewType() { 52 | val items = listOf(Article("article title")) 53 | 54 | val adapter = Klaster.withViewHolder() 55 | .itemCount { items.size } 56 | .viewHolder { viewType: Int, parent: ViewGroup -> 57 | when (viewType) { 58 | TYPE1 -> MyViewHolder(TextView(appContext).apply { 59 | text = "type 1" 60 | id = R.id.item_text 61 | }) 62 | TYPE2 -> MyViewHolder(TextView(appContext).apply { 63 | text = "type 2" 64 | id = R.id.item_text 65 | }) 66 | else -> throw IllegalStateException("Unknown view type $viewType") 67 | } 68 | } 69 | .bind { _ -> } 70 | .build() 71 | 72 | val viewHolder1 = adapter.createViewHolder(parent, TYPE1) 73 | val viewHolder2 = adapter.createViewHolder(parent, TYPE2) 74 | 75 | viewHolder1.itemView.cast().text assertEquals "type 1" 76 | viewHolder2.itemView.cast().text assertEquals "type 2" 77 | } 78 | 79 | @Test 80 | fun bindsViewHolder() { 81 | val viewHolder = createViewHolder() 82 | val items = listOf(Article("article title")) 83 | 84 | val adapter = Klaster.withViewHolder() 85 | .itemCount { items.size } 86 | .defaultViewHolder(layoutInflater) 87 | .bind { position -> 88 | articleTitle.text = "${items[position].title}, position: $position" 89 | } 90 | .build() 91 | 92 | adapter.bindViewHolder(viewHolder, 0) 93 | 94 | viewHolder.cast().articleTitle.text assertEquals "article title, position: 0" 95 | } 96 | 97 | @Test 98 | fun itemCountWorks() { 99 | val items = listOf(Article("article1 title"), Article("article2 title")) 100 | 101 | val adapter = Klaster.withViewHolder() 102 | .itemCount { items.size } 103 | .defaultViewHolder(layoutInflater) 104 | .bind { position -> 105 | articleTitle.text = items[position].title 106 | } 107 | .build() 108 | 109 | adapter.itemCount assertEquals 2 110 | } 111 | 112 | @Test 113 | fun itemCountFromNumberWorks() { 114 | val adapter = Klaster.withViewHolder() 115 | .itemCount(42) 116 | .defaultViewHolder(layoutInflater) 117 | .bind { position -> 118 | articleTitle.text = "position${position + 1}" 119 | } 120 | .build() 121 | 122 | adapter.itemCount assertEquals 42 123 | } 124 | 125 | @Test 126 | fun getItemIdWorks() { 127 | val adapter = Klaster.withViewHolder() 128 | .itemCount(42) 129 | .defaultViewHolder(layoutInflater) 130 | .defaultBind() 131 | .getItemId { position -> position * 2L } 132 | .build() 133 | 134 | adapter.getItemId(10) assertEquals 20L 135 | } 136 | 137 | @Test 138 | fun bindWithPayloadsWorks() { 139 | val viewHolder = createViewHolder() 140 | val mockFunction = mock<((binder: MyViewHolder, position: Int, payloads: MutableList) -> Unit)>() 141 | val adapter = Klaster.withViewHolder() 142 | .itemCount(100) 143 | .defaultViewHolder(layoutInflater) 144 | .defaultBind() 145 | .bind { position, payloads -> 146 | mockFunction(this, position, payloads) 147 | } 148 | .build() 149 | 150 | adapter.onBindViewHolder(viewHolder, 42, listOf("a")) 151 | 152 | verify(mockFunction).invoke(any(), eq(42), eq(mutableListOf("a"))) 153 | } 154 | 155 | @Test 156 | fun getItemViewTypeWorks() { 157 | val mockFunction = mock<((Int) -> Int)>().apply { 158 | whenever(this.invoke(ArgumentMatchers.anyInt())).thenReturn(11) 159 | } 160 | val adapter = Klaster.withViewHolder() 161 | .itemCount(100) 162 | .defaultViewHolder(layoutInflater) 163 | .defaultBind() 164 | .getItemViewType { position -> 165 | mockFunction(position) 166 | } 167 | .build() 168 | 169 | adapter.getItemViewType(42) 170 | 171 | verify(mockFunction).invoke(42) 172 | } 173 | 174 | @Test 175 | fun setHasStableIdsWorks() { 176 | val mockFunction = mock<((Boolean) -> Unit)>() 177 | val adapter = Klaster.withViewHolder() 178 | .itemCount(100) 179 | .defaultViewHolder(layoutInflater) 180 | .defaultBind() 181 | .setHasStableIds { hasStableIds -> 182 | mockFunction(hasStableIds) 183 | } 184 | .build() 185 | 186 | adapter.setHasStableIds(true) 187 | 188 | verify(mockFunction).invoke(true) 189 | } 190 | 191 | @Test 192 | fun onAttachedToRecyclerViewWorks() { 193 | val mockFunction = mock<((recyclerView: RecyclerView) -> Unit)>() 194 | val adapter = Klaster.withViewHolder() 195 | .itemCount(100) 196 | .defaultViewHolder(layoutInflater) 197 | .defaultBind() 198 | .onAttachedToRecyclerView { recyclerView -> 199 | mockFunction(recyclerView) 200 | } 201 | .build() 202 | 203 | adapter.onAttachedToRecyclerView(recyclerViewMock) 204 | 205 | verify(mockFunction).invoke(recyclerViewMock) 206 | } 207 | 208 | @Test 209 | fun onDetachedFromRecyclerViewWorks() { 210 | val mockFunction = mock<((recyclerView: RecyclerView) -> Unit)>() 211 | val adapter = Klaster.withViewHolder() 212 | .itemCount(100) 213 | .defaultViewHolder(layoutInflater) 214 | .defaultBind() 215 | .onDetachedFromRecyclerView { recyclerView -> 216 | mockFunction(recyclerView) 217 | } 218 | .build() 219 | 220 | adapter.onDetachedFromRecyclerView(recyclerViewMock) 221 | 222 | verify(mockFunction).invoke(recyclerViewMock) 223 | } 224 | 225 | @Test 226 | fun onViewAttachedToWindowWorks() { 227 | val viewHolder = createViewHolder() 228 | val mockFunction = mock<((holder: MyViewHolder) -> Unit)>() 229 | val adapter = Klaster.withViewHolder() 230 | .itemCount(100) 231 | .defaultViewHolder(layoutInflater) 232 | .defaultBind() 233 | .onViewAttachedToWindow { holder -> 234 | mockFunction(holder) 235 | } 236 | .build() 237 | 238 | adapter.onViewAttachedToWindow(viewHolder) 239 | 240 | verify(mockFunction).invoke(viewHolder) 241 | } 242 | 243 | @Test 244 | fun onViewDetachedFromWindowWorks() { 245 | val viewHolder = createViewHolder() 246 | val mockFunction = mock<((holder: MyViewHolder) -> Unit)>() 247 | val adapter = Klaster.withViewHolder() 248 | .itemCount(100) 249 | .defaultViewHolder(layoutInflater) 250 | .defaultBind() 251 | .onViewDetachedFromWindow { holder -> 252 | mockFunction(holder) 253 | } 254 | .build() 255 | 256 | adapter.onViewDetachedFromWindow(viewHolder) 257 | 258 | verify(mockFunction).invoke(viewHolder) 259 | } 260 | 261 | @Test 262 | fun onFailedToRecycleViewWorks() { 263 | val viewHolder = createViewHolder() 264 | val mockFunction = mock<((holder: MyViewHolder) -> Boolean)>().apply { 265 | whenever(this.invoke(anyOrNull())).thenReturn(true) 266 | } 267 | val adapter = Klaster.withViewHolder() 268 | .itemCount(100) 269 | .defaultViewHolder(layoutInflater) 270 | .defaultBind() 271 | .onFailedToRecycleView { holder -> 272 | mockFunction(holder) 273 | } 274 | .build() 275 | 276 | adapter.onFailedToRecycleView(viewHolder) 277 | 278 | verify(mockFunction).invoke(viewHolder) 279 | } 280 | 281 | @Test 282 | fun onViewRecycledWorks() { 283 | val viewHolder = createViewHolder() 284 | val mockFunction = mock<((holder: MyViewHolder) -> Unit)>() 285 | val adapter = Klaster.withViewHolder() 286 | .itemCount(100) 287 | .defaultViewHolder(layoutInflater) 288 | .defaultBind() 289 | .onViewRecycled { holder -> 290 | mockFunction(holder) 291 | } 292 | .build() 293 | 294 | adapter.onViewRecycled(viewHolder) 295 | 296 | verify(mockFunction).invoke(viewHolder) 297 | } 298 | 299 | @Test 300 | fun registerAdapterDataObserverWorks() { 301 | val mockFunction = mock<((observer: RecyclerView.AdapterDataObserver) -> Unit)>() 302 | val adapter = Klaster.withViewHolder() 303 | .itemCount(100) 304 | .defaultViewHolder(layoutInflater) 305 | .defaultBind() 306 | .registerAdapterDataObserver { observer -> 307 | mockFunction(observer) 308 | } 309 | .build() 310 | 311 | adapter.registerAdapterDataObserver(adapterDataObserverMock) 312 | 313 | verify(mockFunction).invoke(adapterDataObserverMock) 314 | } 315 | 316 | @Test 317 | fun unregisterAdapterDataObserverWorks() { 318 | val mockFunction = mock<((observer: RecyclerView.AdapterDataObserver) -> Unit)>() 319 | val adapter = Klaster.withViewHolder() 320 | .itemCount(100) 321 | .defaultViewHolder(layoutInflater) 322 | .defaultBind() 323 | .unregisterAdapterDataObserver { observer -> 324 | mockFunction(observer) 325 | } 326 | .build() 327 | 328 | adapter.unregisterAdapterDataObserver(adapterDataObserverMock) 329 | 330 | verify(mockFunction).invoke(adapterDataObserverMock) 331 | } 332 | 333 | private fun createViewHolder(): MyViewHolder { 334 | val viewMock: View = layoutInflater.inflate(R.layout.list_item, null) 335 | return MyViewHolder(viewMock) 336 | } 337 | 338 | } 339 | 340 | private fun KlasterBuilderWithViewHolder.defaultViewHolder(layoutInflater: LayoutInflater): KlasterBuilderWithViewHolder { 341 | return this.viewHolder { _, parent -> 342 | val view = layoutInflater.inflate(R.layout.list_item, parent, false) 343 | MyViewHolder(view) 344 | } 345 | } 346 | 347 | private fun KlasterBuilderWithViewHolder.defaultBind(): KlasterBuilderWithViewHolder { 348 | return bind { position -> 349 | articleTitle.text = "position${position + 1}" 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /klaster-test/src/androidTest/java/com/github/rongi/klaster/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import org.junit.Assert 4 | 5 | infix fun Any.assertIs(clazz: Class) { 6 | Assert.assertEquals(clazz, this.javaClass) 7 | } 8 | 9 | infix fun Any.assertEquals(expected: Any) { 10 | Assert.assertEquals(expected, this) 11 | } 12 | 13 | inline fun Any?.cast(): R = this as R 14 | -------------------------------------------------------------------------------- /klaster-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /klaster-test/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /klaster/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /klaster/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.17" 8 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.library' 13 | apply plugin: 'kotlin-android' 14 | apply plugin: 'kotlin-android-extensions' 15 | // To generate javadoc from Kotlin files. 16 | apply plugin: 'org.jetbrains.dokka-android' 17 | // To see javadoc on jitpack. 18 | apply plugin: 'com.github.dcendents.android-maven' 19 | 20 | group = 'com.github.rongi' 21 | 22 | android { 23 | compileSdkVersion 27 24 | 25 | defaultConfig { 26 | minSdkVersion 16 27 | targetSdkVersion 27 28 | versionCode 1 29 | versionName "1.0" 30 | } 31 | 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | 39 | } 40 | 41 | dependencies { 42 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 43 | implementation 'com.android.support:appcompat-v7:27.1.1' 44 | implementation 'com.android.support:recyclerview-v7:27.1.1' 45 | } 46 | 47 | // To see javadocs on jitpack - START 48 | 49 | // Build a jar with source files. 50 | task sourcesJar(type: Jar) { 51 | from android.sourceSets.main.java.srcDirs 52 | classifier = 'sources' 53 | } 54 | 55 | task javadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { 56 | // failOnError false 57 | // source = android.sourceSets.main.java.sourceFiles 58 | // classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 59 | // classpath += configurations.compile 60 | outputFormat = 'javadoc' 61 | outputDirectory = "$buildDir/docs/javadoc" 62 | sourceDirs = files('src/main/java') 63 | } 64 | 65 | // Build a jar with javadoc. 66 | task javadocJar(type: Jar, dependsOn: javadoc) { 67 | classifier = 'javadoc' 68 | from javadoc.outputDirectory 69 | } 70 | 71 | artifacts { 72 | archives sourcesJar 73 | archives javadocJar 74 | } 75 | 76 | // To see javadocs on jitpack - END 77 | 78 | repositories { 79 | mavenCentral() 80 | } 81 | 82 | androidExtensions { 83 | experimental = true 84 | } 85 | -------------------------------------------------------------------------------- /klaster/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 | -------------------------------------------------------------------------------- /klaster/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /klaster/src/main/java/com/github/rongi/klaster/Klaster.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.support.v7.widget.RecyclerView.ViewHolder 5 | import com.github.rongi.klaster.Klaster.get 6 | import com.github.rongi.klaster.Klaster.withViewHolder 7 | 8 | /** 9 | * Use [get] to build adapters. 10 | * 11 | * Use [withViewHolder] to build adapters using your own implementation of [ViewHolder]. 12 | */ 13 | object Klaster { 14 | 15 | /** 16 | * Returns new [KlasterBuilder]. 17 | */ 18 | fun get(): KlasterBuilder = KlasterBuilder() 19 | 20 | /** 21 | * Returns new [KlasterBuilderWithViewHolder]. 22 | * 23 | * It is intended for the cases when you need a custom [ViewHolder]. 24 | */ 25 | fun withViewHolder(): KlasterBuilderWithViewHolder = KlasterBuilderWithViewHolder() 26 | 27 | } 28 | 29 | class KlasterException(override val message: String) : RuntimeException() 30 | -------------------------------------------------------------------------------- /klaster/src/main/java/com/github/rongi/klaster/KlasterAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.ViewGroup 5 | 6 | internal class KlasterAdapter( 7 | private val createViewHolder: (parent: ViewGroup, viewType: Int) -> VH, 8 | private val bindViewHolder: (viewHolder: VH, position: Int) -> Unit, 9 | private val bindViewHolderWithPayloads: ((viewHolder: VH, position: Int, payloads: MutableList) -> Unit)?, 10 | private val _getItemCount: () -> Int, 11 | private val _getItemId: ((position: Int) -> Long)?, 12 | private val _getItemViewType: ((Int) -> Int)?, 13 | private val _setHasStableIds: ((Boolean) -> Unit)?, 14 | private val _onAttachedToRecyclerView: ((recyclerView: RecyclerView) -> Unit)?, 15 | private val _onDetachedFromRecyclerView: ((recyclerView: RecyclerView) -> Unit)?, 16 | private val _onViewAttachedToWindow: ((holder: VH) -> Unit)?, 17 | private val _onViewDetachedFromWindow: ((holder: VH) -> Unit)?, 18 | private val _onFailedToRecycleView: ((holder: VH) -> Boolean)?, 19 | private val _onViewRecycled: ((holder: VH) -> Unit)?, 20 | private val _registerAdapterDataObserver: ((observer: RecyclerView.AdapterDataObserver) -> Unit)?, 21 | private val _unregisterAdapterDataObserver: ((observer: RecyclerView.AdapterDataObserver) -> Unit)? 22 | ) : RecyclerView.Adapter() { 23 | 24 | override fun getItemCount() = _getItemCount() 25 | 26 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = 27 | createViewHolder.invoke(parent, viewType) 28 | 29 | override fun onBindViewHolder(holder: VH, position: Int) = 30 | bindViewHolder.invoke(holder, position) 31 | 32 | override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList) = 33 | bindViewHolderWithPayloads?.invoke(holder, position, payloads) ?: super.onBindViewHolder(holder, position, payloads) 34 | 35 | override fun getItemId(position: Int): Long = 36 | _getItemId?.invoke(position) ?: super.getItemId(position) 37 | 38 | override fun getItemViewType(position: Int): Int = 39 | _getItemViewType?.invoke(position) ?: super.getItemViewType(position) 40 | 41 | override fun setHasStableIds(hasStableIds: Boolean): Unit = 42 | _setHasStableIds?.invoke(hasStableIds) ?: super.setHasStableIds(hasStableIds) 43 | 44 | override fun onAttachedToRecyclerView(recyclerView: RecyclerView) = 45 | _onAttachedToRecyclerView?.invoke(recyclerView) ?: super.onAttachedToRecyclerView(recyclerView) 46 | 47 | override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = 48 | _onDetachedFromRecyclerView?.invoke(recyclerView) ?: super.onDetachedFromRecyclerView(recyclerView) 49 | 50 | override fun onViewAttachedToWindow(holder: VH) = 51 | _onViewAttachedToWindow?.invoke(holder) ?: super.onViewAttachedToWindow(holder) 52 | 53 | override fun onViewDetachedFromWindow(holder: VH) = 54 | _onViewDetachedFromWindow?.invoke(holder) ?: super.onViewDetachedFromWindow(holder) 55 | 56 | override fun onFailedToRecycleView(holder: VH): Boolean = 57 | _onFailedToRecycleView?.invoke(holder) ?: super.onFailedToRecycleView(holder) 58 | 59 | override fun onViewRecycled(holder: VH) = 60 | _onViewRecycled?.invoke(holder) ?: super.onViewRecycled(holder) 61 | 62 | override fun registerAdapterDataObserver(observer: RecyclerView.AdapterDataObserver) = 63 | _registerAdapterDataObserver?.invoke(observer) ?: super.registerAdapterDataObserver(observer) 64 | 65 | override fun unregisterAdapterDataObserver(observer: RecyclerView.AdapterDataObserver) = 66 | _unregisterAdapterDataObserver?.invoke(observer) ?: super.unregisterAdapterDataObserver(observer) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /klaster/src/main/java/com/github/rongi/klaster/KlasterBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.annotation.LayoutRes 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | 9 | /** 10 | * A builder to make [RecyclerView.Adapter] objects. 11 | */ 12 | class KlasterBuilder internal constructor() { 13 | 14 | private var viewBuilder: ((parent: ViewGroup, viewType: Int) -> View)? = null 15 | 16 | private var binder: ((viewHolder: KlasterViewHolder, position: Int) -> Unit)? = null 17 | 18 | private var binderWithPayloads: (KlasterViewHolder.(position: Int, payloads: MutableList) -> Unit)? = null 19 | 20 | private var getItemCount: (() -> Int)? = null 21 | 22 | private var getItemId: ((position: Int) -> Long)? = null 23 | 24 | private var getItemViewType: ((Int) -> Int)? = null 25 | 26 | private var setHasStableIds: ((Boolean) -> Unit)? = null 27 | 28 | private var onAttachedToRecyclerView: ((recyclerView: RecyclerView) -> Unit)? = null 29 | 30 | private var onDetachedFromRecyclerView: ((recyclerView: RecyclerView) -> Unit)? = null 31 | 32 | private var onViewAttachedToWindow: ((holder: KlasterViewHolder) -> Unit)? = null 33 | 34 | private var onViewDetachedFromWindow: ((holder: KlasterViewHolder) -> Unit)? = null 35 | 36 | private var onFailedToRecycleView: ((holder: KlasterViewHolder) -> Boolean)? = null 37 | 38 | private var onViewRecycled: ((holder: KlasterViewHolder) -> Unit)? = null 39 | 40 | private var registerAdapterDataObserver: ((observer: RecyclerView.AdapterDataObserver) -> Unit)? = null 41 | 42 | private var unregisterAdapterDataObserver: ((observer: RecyclerView.AdapterDataObserver) -> Unit)? = null 43 | 44 | /** 45 | * Specify the layout id to be used to create new views. 46 | * 47 | * For adapters with single view type. 48 | * 49 | * @param initView a function to be called on the view after it was created. Default value is 50 | * and empty function. 51 | */ 52 | fun view(@LayoutRes viewResId: Int, layoutInflater: LayoutInflater, initView: View.() -> Unit = {}): KlasterBuilder { 53 | viewBuilder = { parent: ViewGroup, _: Int -> 54 | layoutInflater.inflate(viewResId, parent, false).apply(initView) 55 | } 56 | 57 | return this 58 | } 59 | 60 | /** 61 | * Specify the function to be used to create views. 62 | * 63 | * For adapters with single view type. 64 | */ 65 | fun viewBy(createView: () -> View): KlasterBuilder { 66 | viewBuilder = { _: ViewGroup, _: Int -> 67 | createView() 68 | } 69 | 70 | return this 71 | } 72 | 73 | /** 74 | * Specify the function to be used to create views. For the cases when you need the 75 | * parent parameter received in [RecyclerView.Adapter.onCreateViewHolder]. 76 | * 77 | * For adapters with single view type. 78 | */ 79 | fun view(createView: (parent: ViewGroup) -> View): KlasterBuilder { 80 | viewBuilder = { parent: ViewGroup, _: Int -> 81 | createView(parent) 82 | } 83 | 84 | return this 85 | } 86 | 87 | /** 88 | * Specify the function to be used to create views. 89 | * 90 | * For adapters with multiple view types. 91 | */ 92 | fun view(createView: (viewType: Int, parent: ViewGroup) -> View): KlasterBuilder { 93 | viewBuilder = { parent: ViewGroup, viewType: Int -> createView(viewType, parent) } 94 | return this 95 | } 96 | 97 | /** 98 | * Specify a function to be used as [RecyclerView.Adapter.getItemCount]. 99 | */ 100 | fun itemCount(getItemsCount: (() -> Int)): KlasterBuilder { 101 | this.getItemCount = getItemsCount 102 | return this 103 | } 104 | 105 | /** 106 | * Specify a number to be used as a return values from [RecyclerView.Adapter.getItemCount]. 107 | */ 108 | fun itemCount(count: Int): KlasterBuilder { 109 | this.getItemCount = { count } 110 | return this 111 | } 112 | 113 | /** 114 | * Specify a function to be used as [RecyclerView.Adapter.onBindViewHolder]. 115 | */ 116 | fun bind(binder: KlasterViewHolder.(position: Int) -> Unit): KlasterBuilder { 117 | this.binder = binder 118 | return this 119 | } 120 | 121 | /** 122 | * Specify a function to be used as onBindViewHolder with payloads. 123 | */ 124 | fun bind(binder: KlasterViewHolder.(position: Int, payloads: MutableList) -> Unit): KlasterBuilder { 125 | this.binderWithPayloads = binder 126 | return this 127 | } 128 | 129 | /** 130 | * Specify a function to be used as [RecyclerView.Adapter.getItemId]. 131 | */ 132 | fun getItemId(getItemId: (position: Int) -> Long): KlasterBuilder { 133 | this.getItemId = getItemId 134 | return this 135 | } 136 | 137 | /** 138 | * Specify a function to be used as [RecyclerView.Adapter.getItemViewType]. 139 | */ 140 | fun getItemViewType(getItemViewType: (Int) -> Int): KlasterBuilder { 141 | this.getItemViewType = getItemViewType 142 | return this 143 | } 144 | 145 | /** 146 | * Specify a function to be used as [RecyclerView.Adapter.setHasStableIds]. 147 | */ 148 | fun setHasStableIds(setHasStableIds: (Boolean) -> Unit): KlasterBuilder { 149 | this.setHasStableIds = setHasStableIds 150 | return this 151 | } 152 | 153 | /** 154 | * Specify a function to be used as [RecyclerView.Adapter.onAttachedToRecyclerView]. 155 | */ 156 | fun onAttachedToRecyclerView(onAttachedToRecyclerView: (recyclerView: RecyclerView) -> Unit): KlasterBuilder { 157 | this.onAttachedToRecyclerView = onAttachedToRecyclerView 158 | return this 159 | } 160 | 161 | /** 162 | * Specify a function to be used as [RecyclerView.Adapter.onDetachedFromRecyclerView]. 163 | */ 164 | fun onDetachedFromRecyclerView(onDetachedFromRecyclerView: (recyclerView: RecyclerView) -> Unit): KlasterBuilder { 165 | this.onDetachedFromRecyclerView = onDetachedFromRecyclerView 166 | return this 167 | } 168 | 169 | /** 170 | * Specify a function to be used as [RecyclerView.Adapter.onViewAttachedToWindow]. 171 | */ 172 | fun onViewAttachedToWindow(onViewAttachedToWindow: (holder: KlasterViewHolder) -> Unit): KlasterBuilder { 173 | this.onViewAttachedToWindow = onViewAttachedToWindow 174 | return this 175 | } 176 | 177 | /** 178 | * Specify a function to be used as [RecyclerView.Adapter.onViewDetachedFromWindow]. 179 | */ 180 | fun onViewDetachedFromWindow(onViewDetachedFromWindow: (holder: KlasterViewHolder) -> Unit): KlasterBuilder { 181 | this.onViewDetachedFromWindow = onViewDetachedFromWindow 182 | return this 183 | } 184 | 185 | /** 186 | * Specify a function to be used as [RecyclerView.Adapter.onFailedToRecycleView]. 187 | */ 188 | fun onFailedToRecycleView(onFailedToRecycleView: (holder: KlasterViewHolder) -> Boolean): KlasterBuilder { 189 | this.onFailedToRecycleView = onFailedToRecycleView 190 | return this 191 | } 192 | 193 | /** 194 | * Specify a function to be used as [RecyclerView.Adapter.onViewRecycled]. 195 | */ 196 | fun onViewRecycled(onViewRecycled: (holder: KlasterViewHolder) -> Unit): KlasterBuilder { 197 | this.onViewRecycled = onViewRecycled 198 | return this 199 | } 200 | 201 | /** 202 | * Specify a function to be used as [RecyclerView.Adapter.registerAdapterDataObserver]. 203 | */ 204 | fun registerAdapterDataObserver(registerAdapterDataObserver: (observer: RecyclerView.AdapterDataObserver) -> Unit): KlasterBuilder { 205 | this.registerAdapterDataObserver = registerAdapterDataObserver 206 | return this 207 | } 208 | 209 | /** 210 | * Specify a function to be used as [RecyclerView.Adapter.unregisterAdapterDataObserver]. 211 | */ 212 | fun unregisterAdapterDataObserver(unregisterAdapterDataObserver: (observer: RecyclerView.AdapterDataObserver) -> Unit): KlasterBuilder { 213 | this.unregisterAdapterDataObserver = unregisterAdapterDataObserver 214 | return this 215 | } 216 | 217 | /** 218 | * Create the [RecyclerView.Adapter] instance. 219 | */ 220 | fun build(): RecyclerView.Adapter { 221 | if (getItemCount == null) throw KlasterException("Get items count function must be provided.") 222 | if (viewBuilder == null) throw KlasterException("View builder must be provided.") 223 | if (binder == null) throw KlasterException("'bind()' must be set.") 224 | 225 | @Suppress("UNCHECKED_CAST") 226 | return KlasterAdapter( 227 | _getItemCount = getItemCount!!, 228 | createViewHolder = { viewGroup, viewType -> 229 | KlasterViewHolder(viewBuilder!!.invoke(viewGroup, viewType)) 230 | }, 231 | bindViewHolder = binder!!, 232 | bindViewHolderWithPayloads = binderWithPayloads, 233 | _getItemId = getItemId, 234 | _getItemViewType = getItemViewType, 235 | _setHasStableIds = setHasStableIds, 236 | _onAttachedToRecyclerView = onAttachedToRecyclerView, 237 | _onDetachedFromRecyclerView = onDetachedFromRecyclerView, 238 | _onViewAttachedToWindow = onViewAttachedToWindow, 239 | _onViewDetachedFromWindow = onViewDetachedFromWindow, 240 | _onFailedToRecycleView = onFailedToRecycleView, 241 | _onViewRecycled = onViewRecycled, 242 | _registerAdapterDataObserver = registerAdapterDataObserver, 243 | _unregisterAdapterDataObserver = unregisterAdapterDataObserver 244 | ) as RecyclerView.Adapter 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /klaster/src/main/java/com/github/rongi/klaster/KlasterBuilderWithViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.support.v7.widget.RecyclerView.ViewHolder 5 | import android.view.ViewGroup 6 | 7 | /** 8 | * A builder to make [RecyclerView.Adapter] objects. 9 | * 10 | * It is intended for the cases when you need a custom [ViewHolder]. 11 | */ 12 | class KlasterBuilderWithViewHolder internal constructor() { 13 | 14 | private var viewHolderBuilder: ((parent: ViewGroup, viewType: Int) -> VH)? = null 15 | 16 | private var binder: ((viewHolder: VH, position: Int) -> Unit)? = null 17 | 18 | private var binderWithPayloads: (VH.(position: Int, payloads: MutableList) -> Unit)? = null 19 | 20 | private var getItemCount: (() -> Int)? = null 21 | 22 | private var getItemId: ((position: Int) -> Long)? = null 23 | 24 | private var getItemViewType: ((Int) -> Int)? = null 25 | 26 | private var setHasStableIds: ((Boolean) -> Unit)? = null 27 | 28 | private var onAttachedToRecyclerView: ((recyclerView: RecyclerView) -> Unit)? = null 29 | 30 | private var onDetachedFromRecyclerView: ((recyclerView: RecyclerView) -> Unit)? = null 31 | 32 | private var onViewAttachedToWindow: ((holder: VH) -> Unit)? = null 33 | 34 | private var onViewDetachedFromWindow: ((holder: VH) -> Unit)? = null 35 | 36 | private var onFailedToRecycleView: ((holder: VH) -> Boolean)? = null 37 | 38 | private var onViewRecycled: ((holder: VH) -> Unit)? = null 39 | 40 | private var registerAdapterDataObserver: ((observer: RecyclerView.AdapterDataObserver) -> Unit)? = null 41 | 42 | private var unregisterAdapterDataObserver: ((observer: RecyclerView.AdapterDataObserver) -> Unit)? = null 43 | 44 | /** 45 | * Specify a function to be used as [RecyclerView.Adapter.onCreateViewHolder]. 46 | */ 47 | fun viewHolder(createViewHolder: (viewType: Int, parent: ViewGroup) -> VH): KlasterBuilderWithViewHolder { 48 | viewHolderBuilder = { parent: ViewGroup, viewType: Int -> 49 | createViewHolder(viewType, parent) 50 | } 51 | 52 | return this 53 | } 54 | 55 | /** 56 | * Specify a function to be used as [RecyclerView.Adapter.getItemCount]. 57 | */ 58 | fun itemCount(getItemsCount: (() -> Int)): KlasterBuilderWithViewHolder { 59 | this.getItemCount = getItemsCount 60 | return this 61 | } 62 | 63 | /** 64 | * Specify a number to be used as a return values from [RecyclerView.Adapter.getItemCount]. 65 | */ 66 | fun itemCount(count: Int): KlasterBuilderWithViewHolder { 67 | this.getItemCount = { count } 68 | return this 69 | } 70 | 71 | /** 72 | * Specify a function to be used as [RecyclerView.Adapter.onBindViewHolder]. 73 | */ 74 | fun bind(binder: VH.(position: Int) -> Unit): KlasterBuilderWithViewHolder { 75 | this.binder = binder 76 | return this 77 | } 78 | 79 | /** 80 | * Specify a function to be used as onBindViewHolder with payloads. 81 | */ 82 | fun bind(binder: VH.(position: Int, payloads: MutableList) -> Unit): KlasterBuilderWithViewHolder { 83 | this.binderWithPayloads = binder 84 | return this 85 | } 86 | 87 | /** 88 | * Specify a function to be used as [RecyclerView.Adapter.getItemId]. 89 | */ 90 | fun getItemId(getItemId: (position: Int) -> Long): KlasterBuilderWithViewHolder { 91 | this.getItemId = getItemId 92 | return this 93 | } 94 | 95 | /** 96 | * Specify a function to be used as [RecyclerView.Adapter.getItemViewType]. 97 | */ 98 | fun getItemViewType(getItemViewType: (Int) -> Int): KlasterBuilderWithViewHolder { 99 | this.getItemViewType = getItemViewType 100 | return this 101 | } 102 | 103 | /** 104 | * Specify a function to be used as [RecyclerView.Adapter.setHasStableIds]. 105 | */ 106 | fun setHasStableIds(setHasStableIds: (Boolean) -> Unit): KlasterBuilderWithViewHolder { 107 | this.setHasStableIds = setHasStableIds 108 | return this 109 | } 110 | 111 | /** 112 | * Specify a function to be used as [RecyclerView.Adapter.onAttachedToRecyclerView]. 113 | */ 114 | fun onAttachedToRecyclerView(onAttachedToRecyclerView: (recyclerView: RecyclerView) -> Unit): KlasterBuilderWithViewHolder { 115 | this.onAttachedToRecyclerView = onAttachedToRecyclerView 116 | return this 117 | } 118 | 119 | /** 120 | * Specify a function to be used as [RecyclerView.Adapter.onDetachedFromRecyclerView]. 121 | */ 122 | fun onDetachedFromRecyclerView(onDetachedFromRecyclerView: (recyclerView: RecyclerView) -> Unit): KlasterBuilderWithViewHolder { 123 | this.onDetachedFromRecyclerView = onDetachedFromRecyclerView 124 | return this 125 | } 126 | 127 | /** 128 | * Specify a function to be used as [RecyclerView.Adapter.onViewAttachedToWindow]. 129 | */ 130 | fun onViewAttachedToWindow(onViewAttachedToWindow: (holder: VH) -> Unit): KlasterBuilderWithViewHolder { 131 | this.onViewAttachedToWindow = onViewAttachedToWindow 132 | return this 133 | } 134 | 135 | /** 136 | * Specify a function to be used as [RecyclerView.Adapter.onViewDetachedFromWindow]. 137 | */ 138 | fun onViewDetachedFromWindow(onViewDetachedFromWindow: (holder: VH) -> Unit): KlasterBuilderWithViewHolder { 139 | this.onViewDetachedFromWindow = onViewDetachedFromWindow 140 | return this 141 | } 142 | 143 | /** 144 | * Specify a function to be used as [RecyclerView.Adapter.onFailedToRecycleView]. 145 | */ 146 | fun onFailedToRecycleView(onFailedToRecycleView: (holder: VH) -> Boolean): KlasterBuilderWithViewHolder { 147 | this.onFailedToRecycleView = onFailedToRecycleView 148 | return this 149 | } 150 | 151 | /** 152 | * Specify a function to be used as [RecyclerView.Adapter.onViewRecycled]. 153 | */ 154 | fun onViewRecycled(onViewRecycled: (holder: VH) -> Unit): KlasterBuilderWithViewHolder { 155 | this.onViewRecycled = onViewRecycled 156 | return this 157 | } 158 | 159 | /** 160 | * Specify a function to be used as [RecyclerView.Adapter.registerAdapterDataObserver]. 161 | */ 162 | fun registerAdapterDataObserver(registerAdapterDataObserver: (observer: RecyclerView.AdapterDataObserver) -> Unit): KlasterBuilderWithViewHolder { 163 | this.registerAdapterDataObserver = registerAdapterDataObserver 164 | return this 165 | } 166 | 167 | /** 168 | * Specify a function to be used as [RecyclerView.Adapter.unregisterAdapterDataObserver]. 169 | */ 170 | fun unregisterAdapterDataObserver(unregisterAdapterDataObserver: (observer: RecyclerView.AdapterDataObserver) -> Unit): KlasterBuilderWithViewHolder { 171 | this.unregisterAdapterDataObserver = unregisterAdapterDataObserver 172 | return this 173 | } 174 | 175 | /** 176 | * Create the [RecyclerView.Adapter] instance. 177 | */ 178 | fun build(): RecyclerView.Adapter { 179 | if (getItemCount == null) throw KlasterException("Get items count function must be provided.") 180 | if (viewHolderBuilder == null) throw KlasterException("View holder builder must be provided.") 181 | if (binder == null) throw KlasterException("bind() must be set.") 182 | 183 | @Suppress("UNCHECKED_CAST") 184 | return KlasterAdapter( 185 | _getItemCount = getItemCount!!, 186 | createViewHolder = viewHolderBuilder!!, 187 | bindViewHolder = binder!!, 188 | bindViewHolderWithPayloads = binderWithPayloads, 189 | _getItemId = getItemId, 190 | _getItemViewType = getItemViewType, 191 | _setHasStableIds = setHasStableIds, 192 | _onAttachedToRecyclerView = onAttachedToRecyclerView, 193 | _onDetachedFromRecyclerView = onDetachedFromRecyclerView, 194 | _onViewAttachedToWindow = onViewAttachedToWindow, 195 | _onViewDetachedFromWindow = onViewDetachedFromWindow, 196 | _onFailedToRecycleView = onFailedToRecycleView, 197 | _onViewRecycled = onViewRecycled, 198 | _registerAdapterDataObserver = registerAdapterDataObserver, 199 | _unregisterAdapterDataObserver = unregisterAdapterDataObserver 200 | ) as RecyclerView.Adapter 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /klaster/src/main/java/com/github/rongi/klaster/KlasterViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import kotlinx.android.extensions.LayoutContainer 6 | 7 | /** 8 | * Simple view holder with empty implementation. 9 | * 10 | * It's designed to be used with Kotlin Android Extensions. 11 | */ 12 | class KlasterViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer 13 | -------------------------------------------------------------------------------- /sample-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 27 7 | defaultConfig { 8 | applicationId "com.github.rongi.klaster.samples" 9 | minSdkVersion 16 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | 25 | // implementation project(":klaster") 26 | implementation 'com.github.rongi:klaster:0.3.5' 27 | 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | implementation 'com.android.support:recyclerview-v7:27.1.1' 30 | 31 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 32 | } 33 | 34 | repositories { 35 | mavenCentral() 36 | } 37 | 38 | androidExtensions { 39 | experimental = true 40 | } 41 | -------------------------------------------------------------------------------- /sample-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/dmitry/dev/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /sample-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 36 | 37 | 41 | 42 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/common/DividerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.common 2 | 3 | import android.content.res.Resources 4 | import android.graphics.Canvas 5 | import android.support.v7.widget.RecyclerView 6 | import com.github.rongi.klaster.samples.R 7 | import java.lang.Math.round 8 | import kotlin.math.roundToInt 9 | 10 | private const val LEFT_PADDING = 12f 11 | private const val RIGHT_PADDING = 12f 12 | 13 | /** 14 | * Divider for RecyclerView 15 | */ 16 | class DividerItemDecoration(resources: Resources) : RecyclerView.ItemDecoration() { 17 | 18 | private val leftPaddingPx = toPixels(LEFT_PADDING, resources) 19 | private val rightPaddingPx = toPixels(RIGHT_PADDING, resources) 20 | private val divider = resources.getDrawable(R.drawable.divider) 21 | 22 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) { 23 | val left = parent.paddingLeft + leftPaddingPx 24 | val right = parent.width - parent.paddingRight - rightPaddingPx 25 | 26 | val childCount = parent.childCount 27 | for (i in 0 until childCount) { 28 | val child = parent.getChildAt(i) 29 | 30 | val params = child.layoutParams as RecyclerView.LayoutParams 31 | 32 | val top = child.bottom + params.bottomMargin + child.translationY.roundToInt() 33 | val bottom = top + divider.intrinsicHeight 34 | 35 | divider.setBounds(left, top, right, bottom) 36 | divider.draw(c) 37 | } 38 | } 39 | 40 | } 41 | 42 | private fun toPixels(dipsValue: Float, resources: Resources?): Int { 43 | return if (resources == null) { 44 | (dipsValue * 2f).toInt() 45 | } else { 46 | round(dipsValue * resources.displayMetrics.density) // For layout visual editor 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/common/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.common 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.support.v7.widget.LinearLayoutManager 7 | import android.support.v7.widget.RecyclerView 8 | import android.view.View 9 | import android.widget.CheckBox 10 | import android.widget.Toast 11 | import kotlin.reflect.KClass 12 | 13 | fun KClass.launch(from: Activity) { 14 | val intent = Intent(from, this.java) 15 | from.startActivity(intent) 16 | } 17 | 18 | fun RecyclerView.init(context: Context) { 19 | layoutManager = LinearLayoutManager(context) 20 | addItemDecoration(DividerItemDecoration(context.resources)) 21 | } 22 | 23 | fun Any.toast(context: Context, duration: Int = Toast.LENGTH_SHORT): Toast { 24 | return Toast.makeText(context, this.toString(), duration).apply { show() } 25 | } 26 | 27 | inline var View.onClick: () -> Unit 28 | set(crossinline callback) = setOnClickListener { callback() } 29 | get() { 30 | throw UnsupportedOperationException() 31 | } 32 | 33 | var View.visible: Boolean 34 | get() { 35 | return this.visibility == View.GONE 36 | } 37 | set(visible) { 38 | this.visibility = if (visible) View.VISIBLE else View.GONE 39 | } 40 | 41 | inline var CheckBox.onCheckedChanged: (checked: Boolean) -> Unit 42 | set(crossinline callback) { 43 | setOnClickListener { 44 | callback(this.isChecked) 45 | } 46 | } 47 | get() { 48 | throw UnsupportedOperationException() 49 | } 50 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/customviewholder/CustomViewHolderExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.customviewholder 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import com.github.rongi.klaster.Klaster 7 | import com.github.rongi.klaster.samples.R 8 | import com.github.rongi.klaster.samples.common.init 9 | import com.github.rongi.klaster.samples.common.onClick 10 | import com.github.rongi.klaster.samples.common.toast 11 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 12 | import com.github.rongi.klaster.samples.main.model.Article 13 | import kotlinx.android.synthetic.main.recycler_view_activity.* 14 | 15 | class CustomViewHolderExampleActivity : AppCompatActivity(), CustomViewHolderExampleView { 16 | 17 | private lateinit var adapter: RecyclerView.Adapter<*> 18 | 19 | private lateinit var presenter: CustomViewHolderExamplePresenter 20 | 21 | private var articles: List
= emptyList() 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(R.layout.recycler_view_activity) 26 | recycler_view.init(this) 27 | 28 | adapter = createAdapter() 29 | 30 | recycler_view.adapter = adapter 31 | 32 | presenter = CustomViewHolderExamplePresenter( 33 | view = this, 34 | articlesProvider = ArticlesProvider 35 | ) 36 | presenter.onViewCreated() 37 | } 38 | 39 | private fun createAdapter() = Klaster.withViewHolder() 40 | .itemCount { articles.size } 41 | .viewHolder { _, parent -> 42 | val view = layoutInflater.inflate(R.layout.list_item, parent, false) 43 | MyViewHolder(view) 44 | } 45 | .bind { position -> 46 | val article = articles[position] 47 | articleTitle.text = article.title 48 | itemView.onClick = { presenter.onArticleClick(article) } 49 | } 50 | .build() 51 | 52 | override fun showArticles(articles: List
) { 53 | this.articles = articles 54 | adapter.notifyDataSetChanged() 55 | } 56 | 57 | override fun showToast(message: String) { 58 | message.toast(this) 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/customviewholder/CustomViewHolderExamplePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.customviewholder 2 | 3 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 4 | import com.github.rongi.klaster.samples.main.model.Article 5 | 6 | class CustomViewHolderExamplePresenter( 7 | private val view: CustomViewHolderExampleView, 8 | private val articlesProvider: ArticlesProvider 9 | ) { 10 | 11 | private var articles: List
= emptyList() 12 | 13 | fun onViewCreated() { 14 | articles = articlesProvider.getArticles() 15 | view.showArticles(articles.toList()) 16 | } 17 | 18 | fun onArticleClick(article: Article) { 19 | view.showToast("Click: ${article.title}") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/customviewholder/CustomViewHolderExampleView.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.customviewholder 2 | 3 | import com.github.rongi.klaster.samples.main.model.Article 4 | 5 | interface CustomViewHolderExampleView { 6 | fun showArticles(articles: List
) 7 | fun showToast(message: String) 8 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/customviewholder/MyViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.customviewholder 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.widget.TextView 6 | import com.github.rongi.klaster.samples.R.id 7 | 8 | class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 9 | val articleTitle: TextView = itemView.findViewById(id.item_text) 10 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/extensions/ExtensionsExample.kt: -------------------------------------------------------------------------------- 1 | import android.view.LayoutInflater 2 | import com.github.rongi.klaster.Klaster 3 | import com.github.rongi.klaster.KlasterBuilder 4 | import com.github.rongi.klaster.KlasterViewHolder 5 | import com.github.rongi.klaster.samples.R 6 | import com.github.rongi.klaster.samples.main.model.Article 7 | import kotlinx.android.synthetic.main.list_item.* 8 | 9 | fun createAdapter(articles: List
, layoutInflater: LayoutInflater) = Klaster.get() 10 | .view(R.layout.list_item, layoutInflater) 11 | .bind(articles) { article, position -> 12 | item_text.text = article.title 13 | } 14 | .build() 15 | 16 | fun KlasterBuilder.bind(items: List, binder: KlasterViewHolder.(item: T, position: Int) -> Unit): KlasterBuilder = 17 | this.itemCount(items.size) 18 | .bind { position -> 19 | val item = items[position] 20 | binder(item, position) 21 | } 22 | 23 | fun createAdapter(articles: () -> List
, layoutInflater: LayoutInflater) = Klaster.get() 24 | .view(R.layout.list_item, layoutInflater) 25 | .bind(articles) { article, position -> 26 | item_text.text = article.title 27 | } 28 | .build() 29 | 30 | fun KlasterBuilder.bind(items: () -> List, binder: KlasterViewHolder.(item: T, position: Int) -> Unit): KlasterBuilder = 31 | this.itemCount { items().size } 32 | .bind { position -> 33 | val item = items()[position] 34 | binder(item, position) 35 | } 36 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/functional/FunctionalExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.functional 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import com.github.rongi.klaster.Klaster 8 | import com.github.rongi.klaster.samples.R 9 | import com.github.rongi.klaster.samples.common.init 10 | import com.github.rongi.klaster.samples.common.onClick 11 | import com.github.rongi.klaster.samples.common.toast 12 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 13 | import com.github.rongi.klaster.samples.main.model.Article 14 | import kotlinx.android.synthetic.main.list_item.* 15 | import kotlinx.android.synthetic.main.recycler_view_activity.* 16 | 17 | class FunctionalExampleActivity : AppCompatActivity(), FunctionalExampleView { 18 | 19 | private lateinit var presenter: FunctionalExamplePresenter 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.recycler_view_activity) 24 | recycler_view.init(this) 25 | 26 | val (adapter, listPresenter) = createAdapter( 27 | layoutInflater = layoutInflater, 28 | onItemClick = { presenter.onArticleClick(it) } 29 | ) 30 | 31 | recycler_view.adapter = adapter 32 | 33 | presenter = FunctionalExamplePresenter( 34 | view = this, 35 | articlesProvider = ArticlesProvider, 36 | listViewPresenter = listPresenter 37 | ) 38 | presenter.onViewCreated() 39 | } 40 | 41 | override fun showToast(message: String) { 42 | message.toast(this) 43 | } 44 | 45 | } 46 | 47 | private fun createAdapter( 48 | layoutInflater: LayoutInflater, 49 | onItemClick: (Article) -> Unit 50 | ): Pair, ListViewPresenter> { 51 | var articles: List
= emptyList() 52 | 53 | val adapter = Klaster.get() 54 | .itemCount { articles.size } 55 | .view(R.layout.list_item, layoutInflater) 56 | .bind { position -> 57 | val article = articles[position] 58 | item_text.text = article.title 59 | itemView.onClick = { onItemClick(article) } 60 | } 61 | .build() 62 | 63 | val presenter = object : ListViewPresenter { 64 | override fun setItems(items: List
) { 65 | articles = items 66 | adapter.notifyDataSetChanged() 67 | } 68 | } 69 | 70 | return adapter to presenter 71 | } 72 | 73 | interface ListViewPresenter { 74 | fun setItems(items: List
) 75 | } 76 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/functional/FunctionalExamplePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.functional 2 | 3 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 4 | import com.github.rongi.klaster.samples.main.model.Article 5 | 6 | class FunctionalExamplePresenter( 7 | private val view: FunctionalExampleView, 8 | private val articlesProvider: ArticlesProvider, 9 | private val listViewPresenter: ListViewPresenter 10 | ) { 11 | 12 | private var articles: List
= emptyList() 13 | 14 | fun onViewCreated() { 15 | articles = articlesProvider.getArticles() 16 | listViewPresenter.setItems(articles.toList()) 17 | } 18 | 19 | fun onArticleClick(article: Article) { 20 | view.showToast("Click: ${article.title}") 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/functional/FunctionalExampleView.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.functional 2 | 3 | interface FunctionalExampleView { 4 | fun showToast(message: String) 5 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/multipleviewtypes/MultipleViewTypesExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.multipleviewtypes 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import com.github.rongi.klaster.Klaster 7 | import com.github.rongi.klaster.samples.R 8 | import com.github.rongi.klaster.samples.common.init 9 | import com.github.rongi.klaster.samples.common.onClick 10 | import com.github.rongi.klaster.samples.common.toast 11 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 12 | import kotlinx.android.synthetic.main.header.* 13 | import kotlinx.android.synthetic.main.list_item.* 14 | import kotlinx.android.synthetic.main.recycler_view_activity.* 15 | 16 | class MultipleViewTypesExampleActivity : AppCompatActivity(), MultipleViewTypesExampleView { 17 | 18 | private lateinit var adapter: RecyclerView.Adapter<*> 19 | 20 | private lateinit var presenter: MultipleViewTypesExamplePresenter 21 | 22 | private var listItems: List = emptyList() 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.recycler_view_activity) 27 | recycler_view.init(this) 28 | 29 | adapter = createAdapter() 30 | 31 | recycler_view.adapter = adapter 32 | 33 | presenter = MultipleViewTypesExamplePresenter( 34 | view = this, 35 | articlesProvider = ArticlesProvider 36 | ) 37 | presenter.onViewCreated() 38 | } 39 | 40 | override fun showListItems(listItems: List) { 41 | this.listItems = listItems 42 | adapter.notifyDataSetChanged() 43 | } 44 | 45 | override fun showToast(message: String) { 46 | message.toast(this) 47 | } 48 | 49 | private fun createAdapter() = Klaster.get() 50 | .itemCount { listItems.size } 51 | .getItemViewType { position -> 52 | when (listItems[position]) { 53 | is ArticleViewData -> 0 54 | is HeaderViewData -> 1 55 | } 56 | } 57 | .view { viewType, parent -> 58 | when (viewType) { 59 | 0 -> layoutInflater.inflate(R.layout.list_item, parent, false) 60 | 1 -> layoutInflater.inflate(R.layout.header, parent, false) 61 | else -> throw IllegalStateException("Unknown view type: $viewType") 62 | } 63 | } 64 | .bind { position -> 65 | val listItem = listItems[position] 66 | 67 | when (listItem) { 68 | is ArticleViewData -> { 69 | item_text.text = listItem.article.title 70 | itemView.onClick = { presenter.onArticleClick(listItem.article) } 71 | } 72 | is HeaderViewData -> { 73 | header_text.text = listItem.headerText 74 | } 75 | } 76 | } 77 | .build() 78 | 79 | } 80 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/multipleviewtypes/MultipleViewTypesExamplePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.multipleviewtypes 2 | 3 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 4 | import com.github.rongi.klaster.samples.main.model.Article 5 | 6 | class MultipleViewTypesExamplePresenter( 7 | private val view: MultipleViewTypesExampleView, 8 | private val articlesProvider: ArticlesProvider 9 | ) { 10 | 11 | private var articles: List
= emptyList() 12 | 13 | fun onViewCreated() { 14 | articles = articlesProvider.getArticles() 15 | 16 | val articlesAsListItems = articles.map(::ArticleViewData) 17 | 18 | val listItems = 19 | listOf(HeaderViewData(headerText = "Today")) + 20 | articlesAsListItems.subList(0, 2) + 21 | listOf(HeaderViewData(headerText = "Yesterday")) + 22 | articlesAsListItems.subList(2, articlesAsListItems.size) 23 | 24 | view.showListItems(listItems) 25 | } 26 | 27 | fun onArticleClick(article: Article) { 28 | view.showToast("Click: ${article.title}") 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/multipleviewtypes/MultipleViewTypesExampleView.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.multipleviewtypes 2 | 3 | import com.github.rongi.klaster.samples.main.model.Article 4 | 5 | sealed class ListItemViewData 6 | 7 | data class HeaderViewData( 8 | val headerText: String 9 | ): ListItemViewData() 10 | 11 | data class ArticleViewData( 12 | val article: Article 13 | ): ListItemViewData() 14 | 15 | interface MultipleViewTypesExampleView { 16 | fun showListItems(listItems: List) 17 | fun showToast(message: String) 18 | } 19 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/simple/SimpleExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.simple 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import com.github.rongi.klaster.Klaster 7 | import com.github.rongi.klaster.samples.R 8 | import com.github.rongi.klaster.samples.common.init 9 | import com.github.rongi.klaster.samples.common.onClick 10 | import com.github.rongi.klaster.samples.common.toast 11 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 12 | import com.github.rongi.klaster.samples.main.model.Article 13 | import kotlinx.android.synthetic.main.list_item.* 14 | import kotlinx.android.synthetic.main.recycler_view_activity.* 15 | 16 | class SimpleExampleActivity : AppCompatActivity(), SimpleExampleView { 17 | 18 | private lateinit var adapter: RecyclerView.Adapter<*> 19 | 20 | private lateinit var presenter: SimpleExamplePresenter 21 | 22 | private var articles: List
= emptyList() 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.recycler_view_activity) 27 | recycler_view.init(this) 28 | 29 | adapter = createAdapter() 30 | 31 | recycler_view.adapter = adapter 32 | 33 | presenter = SimpleExamplePresenter( 34 | view = this, 35 | articlesProvider = ArticlesProvider 36 | ) 37 | presenter.onViewCreated() 38 | } 39 | 40 | override fun showArticles(articles: List
) { 41 | this.articles = articles 42 | adapter.notifyDataSetChanged() 43 | } 44 | 45 | override fun showToast(message: String) { 46 | message.toast(this) 47 | } 48 | 49 | private fun createAdapter() = Klaster.get() 50 | .itemCount { articles.size } 51 | .view(R.layout.list_item, layoutInflater) 52 | .bind { position -> 53 | val article = articles[position] 54 | item_text.text = article.title 55 | itemView.onClick = { presenter.onArticleClick(article) } 56 | } 57 | .build() 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/simple/SimpleExamplePresenter.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.simple 2 | 3 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 4 | import com.github.rongi.klaster.samples.main.model.Article 5 | 6 | class SimpleExamplePresenter( 7 | private val view: SimpleExampleView, 8 | private val articlesProvider: ArticlesProvider 9 | ) { 10 | 11 | private var articles: List
= emptyList() 12 | 13 | fun onViewCreated() { 14 | articles = articlesProvider.getArticles() 15 | view.showArticles(articles.toList()) 16 | } 17 | 18 | fun onArticleClick(article: Article) { 19 | view.showToast("Click: ${article.title}") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/simple/SimpleExampleView.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.simple 2 | 3 | import com.github.rongi.klaster.samples.main.model.Article 4 | 5 | interface SimpleExampleView { 6 | fun showArticles(articles: List
) 7 | fun showToast(message: String) 8 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/subclassing/ArticlesAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.subclassing 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import com.github.rongi.klaster.samples.R 9 | import com.github.rongi.klaster.samples.common.onClick 10 | import com.github.rongi.klaster.samples.examples.subclassing.ArticlesAdapter.ArticlesViewHolder 11 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 12 | import com.github.rongi.klaster.samples.main.model.Article 13 | 14 | /** 15 | * This is not an example of how this library works. This is just for a comparison 16 | * with a vanilla way to declare adapters. 17 | */ 18 | 19 | val articles: List
= ArticlesProvider.getArticles() 20 | 21 | private class ArticlesAdapter( 22 | private val layoutInflater: LayoutInflater 23 | ) : RecyclerView.Adapter() { 24 | 25 | val onItemClick: (() -> Unit)? = null 26 | 27 | override fun getItemCount(): Int { 28 | return articles.size 29 | } 30 | 31 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticlesViewHolder { 32 | val view = layoutInflater.inflate(R.layout.list_item, parent, false) 33 | return ArticlesViewHolder(view) 34 | } 35 | 36 | override fun onBindViewHolder(holder: ArticlesViewHolder, position: Int) { 37 | val article = articles[position] 38 | holder.articleTitle.text = article.title 39 | holder.itemView.onClick = { onItemClick?.invoke() } 40 | } 41 | 42 | private class ArticlesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 43 | val articleTitle: TextView = itemView.findViewById(R.id.item_text) 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/examples/verbose/VerboseExample.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.examples.verbose 2 | 3 | import android.view.LayoutInflater 4 | import com.github.rongi.klaster.Klaster 5 | import com.github.rongi.klaster.samples.R 6 | import com.github.rongi.klaster.samples.main.data.ArticlesProvider 7 | import kotlinx.android.synthetic.main.list_item.* 8 | 9 | val articles = ArticlesProvider.getArticles() 10 | 11 | private const val ITEM_TYPE_1 = 0 12 | private const val ITEM_TYPE_2 = 1 13 | 14 | fun createAdapter(layoutInflater: LayoutInflater) = Klaster.get() 15 | .itemCount { articles.size } 16 | .getItemViewType { position -> position % 2 } 17 | .view { viewType, parent -> 18 | when (viewType) { 19 | ITEM_TYPE_1 -> layoutInflater.inflate(R.layout.list_item1, parent, false) 20 | ITEM_TYPE_2 -> layoutInflater.inflate(R.layout.list_item2, parent, false) 21 | else -> throw IllegalStateException("Unknown type: $viewType") 22 | } 23 | } 24 | .bind { position -> 25 | val article = articles[position] 26 | item_text.text = article.title 27 | } 28 | .bind { position, payloads -> } 29 | .getItemId { position -> position.toLong() } 30 | .setHasStableIds { } 31 | .onAttachedToRecyclerView { } 32 | .onDetachedFromRecyclerView { } 33 | .registerAdapterDataObserver { } 34 | .unregisterAdapterDataObserver { } 35 | .onFailedToRecycleView { true } 36 | .onViewAttachedToWindow { } 37 | .onViewDetachedFromWindow { } 38 | .onViewRecycled { } 39 | .build() 40 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.main 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import com.github.rongi.klaster.Klaster 7 | import com.github.rongi.klaster.samples.R 8 | import com.github.rongi.klaster.samples.common.init 9 | import com.github.rongi.klaster.samples.common.launch 10 | import com.github.rongi.klaster.samples.common.onClick 11 | import com.github.rongi.klaster.samples.examples.customviewholder.CustomViewHolderExampleActivity 12 | import com.github.rongi.klaster.samples.examples.functional.FunctionalExampleActivity 13 | import com.github.rongi.klaster.samples.examples.multipleviewtypes.MultipleViewTypesExampleActivity 14 | import com.github.rongi.klaster.samples.examples.simple.SimpleExampleActivity 15 | import kotlinx.android.synthetic.main.list_item.* 16 | import kotlinx.android.synthetic.main.recycler_view_activity.* 17 | 18 | class MainActivity : AppCompatActivity() { 19 | 20 | private lateinit var adapter: RecyclerView.Adapter<*> 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.recycler_view_activity) 25 | recycler_view.init(this) 26 | 27 | val items = listItems().toMutableList() 28 | 29 | adapter = Klaster.get() 30 | .itemCount(items.size) 31 | .view(R.layout.list_item, layoutInflater) 32 | .bind { position -> 33 | val item = items[position] 34 | item_text.text = item.name 35 | itemView.onClick = { onListItemClick(item.id) } 36 | } 37 | .build() 38 | 39 | recycler_view.adapter = adapter 40 | 41 | adapter.notifyDataSetChanged() 42 | } 43 | 44 | private fun onListItemClick(id: String) = when (id) { 45 | "simple" -> SimpleExampleActivity::class.launch(this) 46 | "multiple item types" -> MultipleViewTypesExampleActivity::class.launch(this) 47 | "view holder" -> CustomViewHolderExampleActivity::class.launch(this) 48 | "functional" -> FunctionalExampleActivity::class.launch(this) 49 | else -> throw IllegalStateException("Unknown id: $id") 50 | } 51 | 52 | private fun listItems(): List { 53 | return listOf( 54 | ExampleListItem( 55 | id = "simple", 56 | name = "Simple Example" 57 | ), 58 | ExampleListItem( 59 | id = "multiple item types", 60 | name = "Multiple Item Types" 61 | ), 62 | ExampleListItem( 63 | id = "view holder", 64 | name = "Custom ViewHolder" 65 | ), 66 | ExampleListItem( 67 | id = "functional", 68 | name = "Functional Example" 69 | ) 70 | ) 71 | } 72 | 73 | } 74 | 75 | class ExampleListItem( 76 | val id: String, 77 | val name: String 78 | ) -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/main/data/Articles.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.main.data 2 | 3 | import com.github.rongi.klaster.samples.main.model.Article 4 | 5 | object ArticlesProvider { 6 | 7 | fun getArticles() = articles 8 | 9 | } 10 | 11 | private val articles = listOf( 12 | Article("TextQL: Execute SQL Against CSV or TSV", id = 1), 13 | Article("Hash-based Signatures: An illustrated Primer", id = 2), 14 | Article("Next generation video: Introducing AV1", id = 3), 15 | Article("The Mathematics of 2048: Optimal Play with Markov Decision Processes", id = 4), 16 | Article("Uber enters dockless bike wars with Jump acquisition", id = 5), 17 | Article("Android container in Chrome OS ", id = 6), 18 | Article("Rational: Or why am I bothering to rewrite nanomsg?", id = 7), 19 | Article("Elm at Pacific Health Dynamics", id = 8), 20 | Article("ACE Submarine Cable Cut Impacts Ten Countries", id = 9), 21 | Article("Weirdstuff Warehouse is closed", id = 10), 22 | Article("A Python Interpreter Written in Python (2016)", id = 11), 23 | Article("OpenAI Charter", id = 12), 24 | Article("Probes Point to Northrop Grumman Errors in January SpaceX Spy-Satellite Failure", id = 13), 25 | Article("DE-Cix – Power failure leads to Downtime in germany", id = 14), 26 | Article("Quartzy (YC S11) Is Hiring Software Engineers (Palo Alto / Remote-US)", id = 15), 27 | Article("A Quantum Computer Simulator in 150 Lines of Code", id = 16), 28 | Article("Reverse Engineering WhatsApp Web", id = 17), 29 | Article("Project from Hell (2008)", id = 18), 30 | Article("Show HN: Wey – A fast, open-source Slack desktop app", id = 19), 31 | Article("Color: From Hex codes to Eyeballs", id = 20) 32 | ) -------------------------------------------------------------------------------- /sample-app/src/main/java/com/github/rongi/klaster/samples/main/model/Article.kt: -------------------------------------------------------------------------------- 1 | package com.github.rongi.klaster.samples.main.model 2 | 3 | data class Article( 4 | val title: String, 5 | val id: Long 6 | ) -------------------------------------------------------------------------------- /sample-app/src/main/res/drawable/delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /sample-app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /sample-app/src/main/res/layout/header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample-app/src/main/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 33 | 34 | 45 | 46 | -------------------------------------------------------------------------------- /sample-app/src/main/res/layout/list_item1.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 33 | 34 | 45 | 46 | -------------------------------------------------------------------------------- /sample-app/src/main/res/layout/list_item2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 33 | 34 | 45 | 46 | -------------------------------------------------------------------------------- /sample-app/src/main/res/layout/recycler_view_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rongi/klaster/db40a14e47ea28def401b1f44c88f88cd70b33f5/sample-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Klaster 3 | Simple Example 4 | Functional Example 5 | Custom ViewHolder Example 6 | Multiple View Types 7 | 8 | -------------------------------------------------------------------------------- /sample-app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample-app', ':klaster', ':klaster-test' 2 | --------------------------------------------------------------------------------