├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ └── styles.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 │ │ ├── java │ │ └── net │ │ │ └── aquadc │ │ │ └── advancedkotlinpatterns │ │ │ ├── common │ │ │ ├── arrays.kt │ │ │ ├── fragments.kt │ │ │ ├── recycler │ │ │ │ ├── BindingViewHolder.kt │ │ │ │ └── ListAdapter.kt │ │ │ └── parcelable.kt │ │ │ ├── feature │ │ │ ├── sealed │ │ │ │ ├── shootALegWithJavaReflectType │ │ │ │ │ └── Shoot.java │ │ │ │ ├── kotlinReflectTypeStyle │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── CallSite.kt │ │ │ │ ├── example │ │ │ │ │ ├── Attachment.kt │ │ │ │ │ ├── StickerHolder.kt │ │ │ │ │ ├── PhotoHolder.kt │ │ │ │ │ ├── LinkHolder.kt │ │ │ │ │ └── AttachmentsFragment.kt │ │ │ │ ├── javaReflectTypeFantasy │ │ │ │ │ ├── Type.java │ │ │ │ │ └── TypeCallSite.java │ │ │ │ └── unions │ │ │ │ │ ├── option │ │ │ │ │ └── Option.kt │ │ │ │ │ └── either │ │ │ │ │ └── Either.kt │ │ │ ├── crossinlineOneLiner │ │ │ │ ├── ClickableSpan.kt │ │ │ │ ├── TextWatcher.kt │ │ │ │ ├── CachedAsyncTaskLoader.kt │ │ │ │ └── LoaderFragment.kt │ │ │ ├── fragments │ │ │ │ ├── dataProviders.kt │ │ │ │ ├── safe │ │ │ │ │ ├── SadFragment.java │ │ │ │ │ ├── FoodFilterAndSortChooserFragment.kt │ │ │ │ │ └── FoodListFragment.kt │ │ │ │ └── unsafe │ │ │ │ │ ├── FoodFilterAndSortChooserFragment.kt │ │ │ │ │ └── FoodListFragment.kt │ │ │ ├── bind │ │ │ │ ├── User.kt │ │ │ │ ├── bind.kt │ │ │ │ ├── ProfileFragment.kt │ │ │ │ └── LocalBroadcastManager.java │ │ │ ├── ankoRecyclerView │ │ │ │ └── AnkoRecyclerViewFragment.kt │ │ │ └── coroutines │ │ │ │ ├── IpApi.kt │ │ │ │ └── CoroutinesFragment.kt │ │ │ ├── extensions.kt │ │ │ ├── MainActivity.kt │ │ │ ├── AdvancedKotlinApp.kt │ │ │ ├── recycler │ │ │ ├── FoodItemHolder.kt │ │ │ ├── createFoodItemHolder.kt │ │ │ ├── FoodItem.kt │ │ │ └── items.kt │ │ │ └── WelcomeFragment.kt │ │ └── AndroidManifest.xml ├── libs │ └── recyclerview-v7-25.3.1-no-proguard.aar ├── proguard-rules.pro └── build.gradle.kts ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── vcs.xml ├── misc.xml ├── codeStyleSettings.xml └── modules.xml ├── gradle.properties ├── .gitignore ├── gradlew.bat ├── readme.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Advanced Kotlin Patterns 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/libs/recyclerview-v7-25.3.1-no-proguard.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/libs/recyclerview-v7-25.3.1-no-proguard.aar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miha-x64/Advanced-Kotlin-Patterns/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 19 23:16:23 MSK 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://repo.gradle.org/gradle/dist-snapshots/gradle-script-kotlin-4.1-20170615174816+0000-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/common/arrays.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.common 2 | 3 | inline fun Collection.mapToArray(transform: (T) -> R): Array { 4 | val itr = iterator() 5 | val array = Array(size) { transform(itr.next()) } 6 | check(!itr.hasNext()) 7 | return array 8 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/shootALegWithJavaReflectType/Shoot.java: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.shootALegWithJavaReflectType; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | public class Shoot { 6 | 7 | public Type getTypeSomewhere() { 8 | return new Type() {}; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/ClickableSpan.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.crossinlineOneLiner 2 | 3 | import android.text.style.ClickableSpan 4 | import android.view.View 5 | 6 | inline fun ClickableSpan( 7 | crossinline onClick: (View) -> Unit 8 | ) = object : ClickableSpan() { 9 | override fun onClick(widget: View) = onClick(widget) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/kotlinReflectTypeStyle/Type.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.kotlinReflectTypeStyle 2 | 3 | import java.lang.reflect.GenericDeclaration 4 | 5 | sealed class Type { 6 | 7 | class Class : Type() 8 | class ParameterizedType : Type() 9 | class GenericArrayType : Type() 10 | class TypeVariable : Type() 11 | class WildcardType : Type() 12 | 13 | } -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/Attachment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.example 2 | 3 | sealed class Attachment 4 | 5 | class Link( 6 | val url: String, 7 | val title: String, 8 | val description: String, 9 | val imageUrl: String? 10 | ) : Attachment() 11 | 12 | class Photo( 13 | val text: String, 14 | val photoUrl: String 15 | ) : Attachment() 16 | 17 | class Sticker( 18 | val imageUrl: String 19 | ) : Attachment() 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/extensions.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns 2 | 3 | import android.app.Fragment 4 | import android.content.Context 5 | import android.support.annotation.AttrRes 6 | import android.util.TypedValue 7 | 8 | private val intArray = intArrayOf(0) 9 | fun Context.attr(@AttrRes attr: Int): TypedValue { 10 | val tv = TypedValue() 11 | val a = obtainStyledAttributes(tv.data, intArray.also { it[0] = attr }) 12 | a.recycle() 13 | return tv 14 | } 15 | 16 | fun Fragment.attr(@AttrRes attr: Int) = 17 | activity.attr(attr) -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/common/fragments.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.common 2 | 3 | import android.app.Fragment 4 | import android.app.FragmentManager 5 | import android.app.FragmentTransaction 6 | import net.aquadc.advancedkotlinpatterns.MainActivity 7 | 8 | fun FragmentManager.replaceAndCommit(newFragment: Fragment) { 9 | beginTransaction() 10 | .replace(MainActivity.FragmentContainerId, newFragment) 11 | .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) 12 | .addToBackStack(null) 13 | .commit() 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/kotlinReflectTypeStyle/CallSite.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.kotlinReflectTypeStyle 2 | 3 | fun main(args: Array) { 4 | val type = getTypeSomewhere() 5 | println(when (type) { 6 | is Type.Class -> "class" 7 | is Type.ParameterizedType -> "parameterized type" 8 | is Type.GenericArrayType -> "generic array type" 9 | is Type.TypeVariable<*> -> "type variable" 10 | is Type.WildcardType -> "wildcard type" 11 | }) 12 | } 13 | 14 | private fun getTypeSomewhere(): Type = throw UnsupportedOperationException() -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/common/recycler/BindingViewHolder.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.common.recycler 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | 6 | abstract class BindingViewHolder( 7 | itemView: View 8 | ) : RecyclerView.ViewHolder(itemView) { 9 | 10 | protected var item: T? = null 11 | private set 12 | 13 | fun bindInternal(item: T) { 14 | this.item = item 15 | bind(item) 16 | } 17 | 18 | fun onRecycleInternal() { 19 | item = null 20 | onRecycle() 21 | } 22 | 23 | abstract fun bind(item: T) 24 | open fun onRecycle() = Unit 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | 6 | class MainActivity : Activity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | 11 | if (savedInstanceState == null) { 12 | fragmentManager 13 | .beginTransaction() 14 | .replace(FragmentContainerId, WelcomeFragment()) 15 | .commit() 16 | } 17 | } 18 | 19 | companion object { 20 | const val FragmentContainerId = android.R.id.content 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/TextWatcher.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.crossinlineOneLiner 2 | 3 | import android.text.Editable 4 | import android.text.TextWatcher 5 | 6 | open class SimpleTextWatcher : TextWatcher { 7 | override fun afterTextChanged(s: Editable) = Unit 8 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit 9 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit 10 | } 11 | 12 | inline fun AfterTextChangedListener( 13 | crossinline afterTextChanged: (s: Editable) -> Unit 14 | ) = object : SimpleTextWatcher() { 15 | override fun afterTextChanged(s: Editable) = afterTextChanged(s) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/dataProviders.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.fragments 2 | 3 | import net.aquadc.advancedkotlinpatterns.recycler.FoodKind 4 | import net.aquadc.advancedkotlinpatterns.recycler.NutritionParameter 5 | import net.aquadc.advancedkotlinpatterns.recycler.foodItems 6 | import net.aquadc.advancedkotlinpatterns.recycler.sortedBy 7 | 8 | fun getSortedByPopularityFoodItems() = foodItems.sortedByDescending { it.popularity } 9 | fun getFilteredAndSortedFoodItems(kinds: Set, sortBy: NutritionParameter, desc: Boolean) = 10 | foodItems 11 | .asSequence() 12 | .filter { it.kind in kinds } 13 | .sortedBy(sortBy, desc) 14 | .toList() -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/javaReflectTypeFantasy/Type.java: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.javaReflectTypeFantasy; 2 | 3 | import java.lang.reflect.GenericArrayType; 4 | import java.lang.reflect.ParameterizedType; 5 | import java.lang.reflect.TypeVariable; 6 | import java.lang.reflect.WildcardType; 7 | 8 | public interface Type { 9 | 10 | void visit(Visitor visitor); 11 | 12 | interface Visitor { 13 | void visitClass(Class klass); 14 | void visitParameterizedType(ParameterizedType parameterizedType); 15 | void visitGenericArrayType(GenericArrayType genericArrayType); 16 | void visitTypeVariable(TypeVariable typeVariable); 17 | void visitWildcardType(WildcardType wildcardType); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/SadFragment.java: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.fragments.safe; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | 6 | @SuppressWarnings("unused") 7 | public class SadFragment extends Fragment { 8 | 9 | /** 10 | * @deprecated no-arg constructor for framework 11 | */ 12 | public SadFragment() {} 13 | 14 | // Error: Avoid non-default constructors in fragments: 15 | // use a default constructor plus 16 | // Fragment#setArguments(Bundle) instead. 17 | public SadFragment(FoodListFragment.DataSource dataSource) { 18 | Bundle bundle = new Bundle(1); 19 | bundle.putParcelable("dataSource", dataSource); 20 | setArguments(bundle); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/AdvancedKotlinApp.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.app.Fragment 6 | import com.jakewharton.picasso.OkHttp3Downloader 7 | import com.squareup.picasso.Picasso 8 | import okhttp3.OkHttpClient 9 | 10 | class AdvancedKotlinApp : Application() { 11 | 12 | lateinit var picasso: Picasso 13 | private set 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | picasso = Picasso.Builder(this) 18 | .downloader(OkHttp3Downloader(OkHttpClient())) 19 | .build() 20 | } 21 | 22 | } 23 | 24 | inline val Activity.app get() = application as AdvancedKotlinApp 25 | inline val Fragment.app get() = activity.application as AdvancedKotlinApp -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/User.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.bind 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import net.aquadc.advancedkotlinpatterns.common.parcelableCreator 6 | import java.util.* 7 | 8 | class User( 9 | val id: UUID, 10 | var email: String, 11 | var name: String 12 | ) : Parcelable { 13 | 14 | override fun describeContents(): Int = 0 15 | override fun writeToParcel(dest: Parcel, flags: Int) { 16 | dest.writeLong(id.mostSignificantBits) 17 | dest.writeLong(id.leastSignificantBits) 18 | dest.writeString(email) 19 | dest.writeString(name) 20 | } 21 | 22 | companion object { 23 | @JvmField val CREATOR = parcelableCreator { 24 | User( 25 | id = UUID(it.readLong(), it.readLong()), 26 | email = it.readString(), 27 | name = it.readString() 28 | ) 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/recycler/FoodItemHolder.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.recycler 2 | 3 | import android.view.View 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import com.squareup.picasso.Picasso 7 | import net.aquadc.advancedkotlinpatterns.common.recycler.BindingViewHolder 8 | 9 | class FoodItemHolder( 10 | itemView: View, 11 | private val photoView: ImageView, 12 | private val titleView: TextView, 13 | private val descriptionView: TextView, 14 | private val nutritionInfoView: TextView, 15 | private val picasso: Picasso 16 | ) : BindingViewHolder(itemView) { 17 | 18 | override fun bind(item: FoodItem) { 19 | picasso.load(item.photoUrl).fit().centerCrop().into(photoView) 20 | titleView.text = item.name 21 | descriptionView.text = item.description 22 | nutritionInfoView.text = item.nutritionInfo.toString() 23 | } 24 | 25 | override fun onRecycle() { 26 | picasso.cancelRequest(photoView) 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/StickerHolder.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.example 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.widget.ImageView 5 | import com.squareup.picasso.Picasso 6 | import net.aquadc.advancedkotlinpatterns.common.recycler.BindingViewHolder 7 | import org.jetbrains.anko.* 8 | 9 | fun AnkoContext<*>.createStickerHolder(picasso: Picasso): BindingViewHolder { 10 | 11 | val view = imageView { 12 | layoutParams = RecyclerView.LayoutParams(matchParent, wrapContent).apply { 13 | margin = dip(8) 14 | } 15 | adjustViewBounds = true 16 | maxWidth = dip(256) 17 | maxHeight = dip(256) 18 | } 19 | 20 | return StickerHolder(view, picasso) 21 | } 22 | 23 | private class StickerHolder( 24 | private val imageView: ImageView, 25 | private val picasso: Picasso 26 | ) : BindingViewHolder(imageView) { 27 | 28 | override fun bind(item: Sticker) { 29 | picasso.load(item.imageUrl).into(imageView) 30 | } 31 | 32 | override fun onRecycle() { 33 | picasso.cancelRequest(imageView) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/ankoRecyclerView/AnkoRecyclerViewFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.ankoRecyclerView 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.support.v7.widget.DividerItemDecoration 6 | import android.support.v7.widget.LinearLayoutManager 7 | import android.view.LayoutInflater 8 | import android.view.ViewGroup 9 | import net.aquadc.advancedkotlinpatterns.app 10 | import net.aquadc.advancedkotlinpatterns.common.recycler.ListAdapter 11 | import net.aquadc.advancedkotlinpatterns.recycler.createFoodItemHolder 12 | import net.aquadc.advancedkotlinpatterns.recycler.foodItems 13 | import org.jetbrains.anko.UI 14 | import org.jetbrains.anko.recyclerview.v7.recyclerView 15 | 16 | class AnkoRecyclerViewFragment : Fragment() { 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 19 | 20 | recyclerView { 21 | id = 1 22 | layoutManager = LinearLayoutManager(activity) 23 | adapter = ListAdapter(foodItems) { createFoodItemHolder(app.picasso) } 24 | addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)) 25 | } 26 | 27 | }.view 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/CachedAsyncTaskLoader.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.crossinlineOneLiner 2 | 3 | import android.content.AsyncTaskLoader 4 | import android.content.Context 5 | import net.aquadc.advancedkotlinpatterns.feature.sealed.unions.either.Either 6 | import net.aquadc.advancedkotlinpatterns.feature.sealed.unions.either.Left 7 | import net.aquadc.advancedkotlinpatterns.feature.sealed.unions.either.Right 8 | 9 | abstract class CachedLoader( 10 | context: Context 11 | ) : AsyncTaskLoader>(context) { 12 | 13 | private var cached: Right? = null 14 | 15 | final override fun onStartLoading() { 16 | val cached = cached 17 | if (cached == null) forceLoad() 18 | else deliverResult(cached) 19 | } 20 | 21 | final override fun loadInBackground(): Either = try { 22 | Right(load()).also { cached = it } 23 | } catch (e: Throwable) { 24 | Left(e) 25 | } 26 | 27 | @Throws(Throwable::class) 28 | abstract fun load(): D 29 | 30 | } 31 | 32 | inline fun CachedAsyncTaskLoader( 33 | context: Context, crossinline load: () -> D 34 | ) = object : CachedLoader(context) { 35 | override fun load(): D = load.invoke() 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/javaReflectTypeFantasy/TypeCallSite.java: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.javaReflectTypeFantasy; 2 | 3 | import java.lang.reflect.GenericArrayType; 4 | import java.lang.reflect.ParameterizedType; 5 | import java.lang.reflect.TypeVariable; 6 | import java.lang.reflect.WildcardType; 7 | 8 | public class TypeCallSite { 9 | 10 | public static void main(String[] args) { 11 | getTypeSomewhere().visit(new Type.Visitor() { 12 | @Override 13 | public void visitClass(Class klass) { 14 | 15 | } 16 | 17 | @Override 18 | public void visitParameterizedType(ParameterizedType parameterizedType) { 19 | 20 | } 21 | 22 | @Override 23 | public void visitGenericArrayType(GenericArrayType genericArrayType) { 24 | 25 | } 26 | 27 | @Override 28 | public void visitTypeVariable(TypeVariable typeVariable) { 29 | 30 | } 31 | 32 | @Override 33 | public void visitWildcardType(WildcardType wildcardType) { 34 | 35 | } 36 | }); 37 | } 38 | 39 | private static Type getTypeSomewhere() { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 12 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 13 | 14 | # User-specific stuff: 15 | .idea/**/workspace.xml 16 | .idea/**/tasks.xml 17 | .idea/dictionaries 18 | 19 | # Sensitive or high-churn files: 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.xml 23 | .idea/**/dataSources.local.xml 24 | .idea/**/sqlDataSources.xml 25 | .idea/**/dynamic.xml 26 | .idea/**/uiDesigner.xml 27 | 28 | # Gradle: 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # CMake 33 | cmake-build-debug/ 34 | 35 | # Mongo Explorer plugin: 36 | .idea/**/mongoSettings.xml 37 | 38 | ## File-based project format: 39 | *.iws 40 | 41 | ## Plugin-specific files: 42 | 43 | # IntelliJ 44 | out/ 45 | 46 | # mpeltonen/sbt-idea plugin 47 | .idea_modules/ 48 | 49 | # JIRA plugin 50 | atlassian-ide-plugin.xml 51 | 52 | # Cursive Clojure plugin 53 | .idea/replstate.xml 54 | 55 | # Crashlytics plugin (for Android Studio and IntelliJ) 56 | com_crashlytics_export_strings.xml 57 | crashlytics.properties 58 | crashlytics-build.properties 59 | fabric.properties -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/common/recycler/ListAdapter.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.common.recycler 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.ViewGroup 5 | import org.jetbrains.anko.AnkoContext 6 | 7 | class ListAdapter( 8 | private val items: List, 9 | private val viewTypeOf: (T) -> Int = { 0 }, 10 | private val holderFactory: AnkoContext>.(viewType: Int) -> BindingViewHolder 11 | ) : RecyclerView.Adapter>() { 12 | 13 | private var context: AnkoContext>? = null 14 | 15 | override fun getItemCount(): Int = items.size 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder { 18 | val context = context ?: AnkoContext.Companion.createReusable(parent.context, this).also { context = it } 19 | @Suppress("UNCHECKED_CAST") // it's a client's responsibility to return correct viewHolder 20 | return context.holderFactory(viewType) as BindingViewHolder 21 | } 22 | 23 | override fun getItemViewType(position: Int): Int = 24 | viewTypeOf(items[position]) 25 | 26 | override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { 27 | holder.bindInternal(items[position]) 28 | } 29 | 30 | override fun onViewRecycled(holder: BindingViewHolder) { 31 | holder.onRecycleInternal() 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/PhotoHolder.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.example 2 | 3 | import android.view.Gravity 4 | import android.view.View 5 | import android.widget.FrameLayout 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import com.squareup.picasso.Picasso 9 | import net.aquadc.advancedkotlinpatterns.common.recycler.BindingViewHolder 10 | import org.jetbrains.anko.* 11 | 12 | fun AnkoContext<*>.createPhotoHolder(picasso: Picasso): BindingViewHolder { 13 | 14 | var imageView: ImageView? = null 15 | var textView: TextView? = null 16 | 17 | frameLayout { 18 | layoutParams = FrameLayout.LayoutParams(matchParent, wrapContent).apply { 19 | margin = dip(8) 20 | } 21 | 22 | imageView = imageView { 23 | adjustViewBounds = true 24 | }.lparams(width = matchParent) 25 | 26 | textView = textView { 27 | padding = dip(8) 28 | backgroundColor = 0x88_FFFFFF.toInt() 29 | }.lparams(width = matchParent, gravity = Gravity.BOTTOM) 30 | } 31 | 32 | return PhotoHolder(view, imageView!!, textView!!, picasso) 33 | } 34 | 35 | private class PhotoHolder( 36 | itemView: View, 37 | private val imageView: ImageView, 38 | private val textView: TextView, 39 | private val picasso: Picasso 40 | ) : BindingViewHolder(itemView) { 41 | 42 | override fun bind(item: Photo) { 43 | picasso.load(item.photoUrl).into(imageView) 44 | textView.text = item.text 45 | } 46 | 47 | override fun onRecycle() { 48 | super.onRecycle() 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/bind.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.bind 2 | 3 | import android.app.Fragment 4 | import android.content.BroadcastReceiver 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.IntentFilter 8 | import android.view.View 9 | 10 | const val ACTION_USER_UPDATED = "net.aquadc.advancedkotlinpatterns.feature.bind.ACTION_USER_UPDATED" 11 | 12 | inline fun T.bind(updateAction: String, crossinline bind: (T)->Unit) { 13 | addOnAttachStateChangeListener(object : LocalBroadcastBinder(this@bind, updateAction) { 14 | override fun bind(view: T) = bind(view) 15 | }) 16 | } 17 | 18 | fun Context.updated(updateAction: String) { 19 | LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(updateAction)) 20 | } 21 | 22 | @Suppress("NOTHING_TO_INLINE") 23 | inline fun Fragment.updated(updateAction: String) = activity.updated(updateAction) 24 | 25 | abstract class LocalBroadcastBinder( 26 | private val view: T, 27 | action: String 28 | ) : BroadcastReceiver(), View.OnAttachStateChangeListener { 29 | 30 | private val filter = IntentFilter(action) 31 | 32 | final override fun onViewAttachedToWindow(v: View) { 33 | LocalBroadcastManager.getInstance(v.context).registerReceiver(this, filter) 34 | bind(view) 35 | } 36 | 37 | final override fun onViewDetachedFromWindow(v: View) { 38 | LocalBroadcastManager.getInstance(v.context).unregisterReceiver(this) 39 | } 40 | 41 | final override fun onReceive(context: Context?, intent: Intent?) { 42 | bind(view) 43 | } 44 | 45 | protected abstract fun bind(view: T) 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/recycler/createFoodItemHolder.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.recycler 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import com.squareup.picasso.Picasso 7 | import org.jetbrains.anko.* 8 | 9 | // https://meoyawn.tumblr.com/post/154221726637/anko-viewholders-and-applicative-lifting 10 | // 'I hate my type checker' 11 | fun AnkoContext<*>.createFoodItemHolder(picasso: Picasso): FoodItemHolder { 12 | var photoView: ImageView? = null 13 | var titleView: TextView? = null 14 | var descriptionView: TextView? = null 15 | var nutritionInfoView: TextView? = null 16 | 17 | relativeLayout { 18 | layoutParams = RecyclerView.LayoutParams(matchParent, wrapContent) 19 | 20 | photoView = imageView { 21 | id = 1 22 | }.lparams(dip(150), dip(150)) 23 | 24 | titleView = textView { 25 | id = 2 26 | textSize = 20f 27 | textColor = 0xFF000000.toInt() 28 | }.lparams { 29 | rightOf(1) 30 | topMargin = dip(8) 31 | horizontalMargin = dip(8) 32 | } 33 | 34 | descriptionView = textView { 35 | id = 3 36 | textSize = 16f 37 | }.lparams { 38 | rightOf(1) 39 | below(2) 40 | horizontalMargin = dip(8) 41 | bottomMargin = dip(4) 42 | } 43 | 44 | nutritionInfoView = textView { 45 | textSize = 14f 46 | }.lparams { 47 | rightOf(1) 48 | below(3) 49 | horizontalMargin = dip(8) 50 | bottomMargin = dip(8) 51 | } 52 | } 53 | 54 | return FoodItemHolder(view, photoView!!, titleView!!, descriptionView!!, nutritionInfoView!!, picasso) 55 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/unions/option/Option.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.unions.option 2 | 3 | /** 4 | * Represents Scala-style optional type Option 5 | */ 6 | sealed class Option 7 | 8 | class Some( 9 | val value: T 10 | ) : Option() { 11 | override fun hashCode(): Int = value.hashCode() + 1 12 | override fun equals(other: Any?): Boolean = other is Some<*> && value == other.value 13 | override fun toString(): String = "Some($value)" 14 | operator fun component1() = value 15 | } 16 | 17 | object None : Option() { 18 | override fun hashCode(): Int = 1 19 | override fun equals(other: Any?): Boolean = other === this 20 | override fun toString(): String = "None" 21 | } 22 | 23 | inline fun Option.ifPresent(consumer: (T) -> Unit) = when (this) { 24 | is Some -> consumer(value) 25 | None -> Unit 26 | } 27 | 28 | inline fun Option.filter(predicate: (T) -> Boolean) = when (this) { 29 | is Some -> if (predicate(value)) this else None 30 | None -> None 31 | } 32 | 33 | inline fun Option.map(transform: (T) -> R) = when (this) { 34 | is Some -> Some(transform(value)) 35 | None -> None 36 | } 37 | 38 | fun Option.map(transform: Option<(T) -> R>) = when { 39 | this is Some && transform is Some -> Some(transform.value(value)) 40 | else -> None 41 | } 42 | 43 | inline fun Option.flatMap(transform: (T) -> Option) = when (this) { 44 | is Some -> transform(value) 45 | None -> None 46 | } 47 | 48 | fun Option.orElse(fallback: T) = when (this) { 49 | is Some -> value 50 | None -> fallback 51 | } 52 | 53 | fun Option.orElse(lazyFallback: () -> T) = when (this) { 54 | is Some -> value 55 | None -> lazyFallback() 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/coroutines/IpApi.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.coroutines 2 | 3 | import okhttp3.ResponseBody 4 | import org.json.JSONObject 5 | import retrofit2.Call 6 | import retrofit2.Converter 7 | import retrofit2.Retrofit 8 | import retrofit2.http.GET 9 | import java.lang.reflect.Type 10 | 11 | private val impl = 12 | Retrofit.Builder() 13 | .baseUrl("http://ip-api.com/") 14 | .addConverterFactory(object : Converter.Factory() { 15 | override fun responseBodyConverter(type: Type, annotations: Array?, retrofit: Retrofit): Converter? = 16 | when (type) { 17 | IpApi.Location::class.java -> Converter { 18 | val json = JSONObject(it.string()) 19 | IpApi.Location( 20 | country = json.getString("country"), 21 | city = json.getString("city"), 22 | latitude = json.getDouble("lat"), 23 | longitude = json.getDouble("lon") 24 | ) 25 | } 26 | else -> throw UnsupportedOperationException("unsupported type: $type") 27 | } 28 | }) 29 | .build() 30 | .create(IpService::class.java) 31 | 32 | object IpApi : IpService by impl { 33 | 34 | class Location( 35 | val country: String, 36 | val city: String, 37 | val latitude: Double, 38 | val longitude: Double 39 | ) 40 | 41 | } 42 | 43 | interface IpService { 44 | @get:GET("json") 45 | val myLocation: Call 46 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/common/parcelable.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.common 2 | 3 | import android.os.Bundle 4 | import android.os.Parcel 5 | import android.os.Parcelable 6 | import java.util.* 7 | 8 | inline fun parcelableCreator( 9 | crossinline create: (Parcel)->T 10 | ) = object : Parcelable.Creator { 11 | override fun newArray(size: Int): Array = 12 | arrayOfNulls(size) 13 | override fun createFromParcel(src: Parcel): T = 14 | create(src) 15 | } 16 | 17 | interface BoringParcelable : Parcelable { 18 | override fun describeContents(): Int = 0 19 | } 20 | 21 | fun > Bundle.putEnumSet(key: String, set: Set) { 22 | putStringArray(key, set.mapToArray { it.name }) 23 | } 24 | 25 | inline fun > Bundle.getEnumSet(key: String): Set { 26 | val names = getStringArray(key) 27 | val set = EnumSet.noneOf(E::class.java) 28 | names.forEach { set.add(enumValueOf(it)) } 29 | return set 30 | } 31 | 32 | fun > Parcel.writeEnumSet(set: Set) { 33 | writeStringArray(set.mapToArray { it.name }) 34 | } 35 | 36 | inline fun > Parcel.readEnumSet(): Set { 37 | val names = createStringArray() 38 | val set = EnumSet.noneOf(E::class.java) 39 | names.forEach { set.add(enumValueOf(it)) } 40 | return set 41 | } 42 | 43 | fun Parcel.writeBoolean(value: Boolean) { 44 | writeByte(if (value) 1 else 0) 45 | } 46 | 47 | fun Parcel.readBoolean(): Boolean { 48 | val value = readByte() 49 | return when (value) { 50 | 0.toByte() -> false 51 | 1.toByte() -> true 52 | else -> throw IllegalArgumentException("Cannot read byte value $value as Boolean, 0 or 1 expected.") 53 | } 54 | } 55 | 56 | fun Parcel.writeEnum(value: Enum<*>) { 57 | writeString(value.name) 58 | } 59 | 60 | inline fun > Parcel.readEnum(): E { 61 | return enumValueOf(readString()) 62 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | 2 | -dontskipnonpubliclibraryclasses 3 | -dontpreverify 4 | -optimizationpasses 5 5 | -overloadaggressively 6 | -allowaccessmodification 7 | 8 | -repackageclasses "_" 9 | -renamesourcefileattribute "_" 10 | 11 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics { 12 | public static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String); 13 | public static void checkFieldIsNotNull(java.lang.Object, java.lang.String); 14 | public static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String); 15 | public static void checkNotNull(java.lang.Object); 16 | public static void checkNotNull(java.lang.Object, java.lang.String); 17 | public static void checkNotNullExpressionValue(java.lang.Object, java.lang.String); 18 | public static void checkNotNullParameter(java.lang.Object, java.lang.String); 19 | public static void checkParameterIsNotNull(java.lang.Object, java.lang.String); 20 | public static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String); 21 | public static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String); 22 | } 23 | 24 | -keepclassmembers class * implements android.os.Parcelable { 25 | public static final android.os.Parcelable$Creator CREATOR; 26 | } 27 | 28 | -dontwarn okio.** 29 | -dontwarn com.squareup.picasso.** 30 | 31 | -keepclassmembers enum * { 32 | public static **[] values(); 33 | # public static ** valueOf(java.lang.String); 34 | } 35 | 36 | 37 | -dontwarn okio.** 38 | -dontwarn javax.annotation.** 39 | 40 | # Platform calls Class.forName on types which do not exist on Android to determine platform. 41 | -dontnote retrofit2.Platform 42 | # Platform used when running on Java 8 VMs. Will not be used at runtime. 43 | -dontwarn retrofit2.Platform$Java8 44 | # Retain generic type information for use by reflection by converters and adapters. 45 | -keepattributes Signature 46 | # Retain declared checked exceptions for use by a Proxy instance. 47 | #-keepattributes Exceptions 48 | 49 | -keepclassmembernames class kotlinx.** { 50 | volatile ; 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/unions/either/Either.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.unions.either 2 | 3 | sealed class Either 4 | 5 | class Left( 6 | val value: T 7 | ) : Either() { 8 | override fun hashCode(): Int = 3 * value.hashCode() 9 | override fun equals(other: Any?): Boolean = other is Left<*> && value == other.value 10 | override fun toString(): String = "Left($value)" 11 | } 12 | 13 | class Right( 14 | val value: T 15 | ): Either() { 16 | override fun hashCode(): Int = 7 * value.hashCode() 17 | override fun equals(other: Any?): Boolean = other is Right<*> && value == other.value 18 | override fun toString(): String = "Right($value)" 19 | } 20 | 21 | inline fun Either.let(transformLeft: (L) -> RR, transformRight: (R) -> RR) = when (this) { 22 | is Left -> transformLeft(value) 23 | is Right -> transformRight(value) 24 | } 25 | 26 | inline fun Either.mapLeft(transform: (L) -> TL) = when (this) { 27 | is Left -> Left(transform(value)) 28 | is Right -> this 29 | } 30 | 31 | inline fun Either.mapRight(transform: (R) -> TR) = when (this) { 32 | is Left -> this 33 | is Right -> Right(transform(value)) 34 | } 35 | 36 | inline fun Either.map( 37 | transformLeft: (L) -> TL, transformRight: (R) -> TR 38 | ) = when (this) { 39 | is Left -> Left(transformLeft(value)) 40 | is Right -> Right(transformRight(value)) 41 | } 42 | 43 | fun Either.leftOrElse(fallback: L) = when (this) { 44 | is Left -> value 45 | is Right -> fallback 46 | } 47 | 48 | inline fun Either.leftOrElse(lazyFallback: () -> L) = when (this) { 49 | is Left -> value 50 | is Right -> lazyFallback() 51 | } 52 | 53 | fun Either<*, R>.rightOrElse(fallback: R) = when (this) { 54 | is Left -> fallback 55 | is Right -> value 56 | } 57 | 58 | inline fun Either<*, R>.rightOrElse(lazyFallback: () -> R) = when (this) { 59 | is Left -> lazyFallback() 60 | is Right -> value 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/ProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.bind 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import android.widget.EditText 8 | import android.widget.FrameLayout 9 | import org.jetbrains.anko.* 10 | import java.util.* 11 | 12 | class ProfileFragment : Fragment() { 13 | 14 | private lateinit var user: User 15 | private lateinit var emailInput: EditText 16 | private lateinit var nameInput: EditText 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 19 | 20 | user = savedInstanceState?.getParcelable("user") ?: User(UUID.randomUUID(), "", "") 21 | 22 | verticalLayout { 23 | layoutParams = FrameLayout.LayoutParams(matchParent, matchParent) 24 | padding = dip(16) 25 | 26 | textView { 27 | textSize = 20f 28 | padding = dip(8) 29 | bind(ACTION_USER_UPDATED) { 30 | it.text = when { 31 | user.name.isNotBlank() && user.email.isNotBlank() -> "${user.name} (${user.email})" 32 | user.name.isNotBlank() -> user.name 33 | user.email.isNotBlank() -> user.email 34 | else -> user.id.toString() 35 | } 36 | } 37 | }.lparams(matchParent, wrapContent) 38 | 39 | emailInput = editText { 40 | id = 1 41 | hint = "E-mail" 42 | }.lparams(matchParent, wrapContent) 43 | 44 | nameInput = editText { 45 | id = 2 46 | hint = "Name" 47 | }.lparams(matchParent, wrapContent) 48 | 49 | button("Save") { 50 | setOnClickListener { saveButtonClicked() } 51 | } 52 | } 53 | 54 | }.view 55 | 56 | override fun onSaveInstanceState(outState: Bundle) { 57 | super.onSaveInstanceState(outState) 58 | outState.putParcelable("user", user) 59 | } 60 | 61 | private fun saveButtonClicked() { 62 | user.email = emailInput.text.toString() 63 | user.name = nameInput.text.toString() 64 | updated(ACTION_USER_UPDATED) 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/recycler/FoodItem.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.recycler 2 | 3 | import java.util.Collections.unmodifiableList 4 | 5 | /** 6 | * Item of something that can be devoured and digested by a live creature. 7 | * @param photoUrl URL of a photo, 256...500 px 8 | * @param name name of the dish 9 | * @param description brief info about composition, cooking technology, geography & history of a product 10 | * @param nutritionInfo nutritional value details of a product 11 | * @param kind whether this item is a meal, a drink or whatever 12 | * @param popularity number of search results in Google, where query is [name] 13 | */ 14 | class FoodItem( 15 | val photoUrl: String, 16 | val name: CharSequence, 17 | val description: CharSequence, 18 | val nutritionInfo: NutritionInfo, 19 | val kind: FoodKind, 20 | val popularity: Long 21 | ) 22 | 23 | /** 24 | * Nutrition info per 100g of product. 25 | * @param energy in kilo-calories 26 | * @param fats in grams 27 | * @param carbohydrates in grams 28 | * @param proteins in grams 29 | */ 30 | class NutritionInfo( 31 | val energy: Double, 32 | val fats: Double, 33 | val carbohydrates: Double, 34 | val proteins: Double 35 | ) { 36 | 37 | // todo: hashCode & equals 38 | 39 | /** 40 | * Returns human-readable representation. 41 | * You can't see it but should be aware: it contains no-break spaces (\u00A0). 42 | */ 43 | override fun toString() = 44 | "Energy: $energy kcal, " + 45 | "fats: $fats g, " + 46 | "carbohydrates: $carbohydrates g, " + 47 | "proteins: $proteins g" 48 | } 49 | 50 | fun NutritionInfo.valueOf(parameter: NutritionParameter) = when (parameter) { 51 | NutritionParameter.Energy -> energy 52 | NutritionParameter.Fats -> fats 53 | NutritionParameter.Carbohydrates -> carbohydrates 54 | NutritionParameter.Proteins -> proteins 55 | } 56 | 57 | fun Sequence.sortedBy(parameter: NutritionParameter, desc: Boolean) = 58 | if (desc) sortedByDescending { it.nutritionInfo.valueOf(parameter) } 59 | else sortedBy { it.nutritionInfo.valueOf(parameter) } 60 | 61 | enum class NutritionParameter { 62 | Energy, Fats, Carbohydrates, Proteins; 63 | 64 | companion object { 65 | val Values: List = unmodifiableList(values().toList()) 66 | } 67 | } 68 | 69 | enum class FoodKind { 70 | Meal, Drink; 71 | 72 | companion object { 73 | val Values: List = unmodifiableList(values().toList()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/WelcomeFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.ArrayAdapter 9 | import android.widget.FrameLayout 10 | import net.aquadc.advancedkotlinpatterns.common.replaceAndCommit 11 | import net.aquadc.advancedkotlinpatterns.feature.ankoRecyclerView.AnkoRecyclerViewFragment 12 | import net.aquadc.advancedkotlinpatterns.feature.bind.ProfileFragment 13 | import net.aquadc.advancedkotlinpatterns.feature.coroutines.CoroutinesFragment 14 | import net.aquadc.advancedkotlinpatterns.feature.crossinlineOneLiner.LoaderFragment 15 | import net.aquadc.advancedkotlinpatterns.feature.fragments.safe.FoodFilterAndSortChooserFragment 16 | import net.aquadc.advancedkotlinpatterns.feature.fragments.safe.FoodListFragment 17 | import net.aquadc.advancedkotlinpatterns.feature.sealed.example.AttachmentsFragment 18 | import org.jetbrains.anko.UI 19 | import org.jetbrains.anko.listView 20 | import org.jetbrains.anko.matchParent 21 | 22 | class WelcomeFragment : Fragment() { 23 | 24 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View = UI { 25 | listView { 26 | layoutParams = FrameLayout.LayoutParams(matchParent, matchParent) 27 | 28 | val itemAdapter = ArrayAdapter(activity, android.R.layout.simple_list_item_1, listOf( 29 | Item("Food list (RecyclerView with Anko)", ::AnkoRecyclerViewFragment), 30 | Item("Edit user (Reactive binding)", ::ProfileFragment), 31 | Item("Food by popularity (safe fragment)", { FoodListFragment(FoodListFragment.DataSource.Popular) }), 32 | Item("Food filter (safe fragment)", ::FoodFilterAndSortChooserFragment), 33 | Item("A fragment with CachedAsyncTaskLoader, ClickableSpan, and AfterTextChangedListener (crossinline)", ::LoaderFragment), 34 | Item("Coroutines", ::CoroutinesFragment), 35 | Item("Sealed classes (Attachments adapter)", ::AttachmentsFragment) 36 | )) 37 | adapter = itemAdapter 38 | 39 | setOnItemClickListener { _, _, position, _ -> 40 | fragmentManager 41 | .replaceAndCommit(itemAdapter.getItem(position).createFragment()) 42 | } 43 | } 44 | }.view 45 | 46 | private class Item( 47 | private val label: String, 48 | val createFragment: () -> Fragment 49 | ) { 50 | override fun toString() = label 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/FoodFilterAndSortChooserFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.fragments.safe 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import android.widget.ArrayAdapter 8 | import android.widget.CheckBox 9 | import android.widget.FrameLayout 10 | import net.aquadc.advancedkotlinpatterns.common.replaceAndCommit 11 | import net.aquadc.advancedkotlinpatterns.recycler.FoodKind 12 | import net.aquadc.advancedkotlinpatterns.recycler.NutritionParameter 13 | import org.jetbrains.anko.* 14 | import java.util.* 15 | 16 | class FoodFilterAndSortChooserFragment : Fragment() { 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 19 | 20 | verticalLayout { 21 | layoutParams = FrameLayout.LayoutParams(matchParent, wrapContent) 22 | padding = dip(16) 23 | 24 | textView("Sort by").lparams(width = matchParent) 25 | 26 | val nutritionParameterSpinner = spinner { 27 | id = 1 28 | adapter = ArrayAdapter(activity, android.R.layout.simple_list_item_1, NutritionParameter.Values).apply { 29 | setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) 30 | } 31 | } 32 | 33 | val descendingCheck = checkBox("Descending") { 34 | id = 2 35 | isChecked = true 36 | } 37 | 38 | textView("Include").lparams(width = matchParent) { 39 | topMargin = dip(16) 40 | } 41 | 42 | var lastId = 2 43 | val foodChecks = EnumMap(FoodKind::class.java) 44 | FoodKind.Values.forEach { 45 | foodChecks[it] = checkBox(it.name) { 46 | id = ++lastId 47 | isChecked = true 48 | } 49 | } 50 | 51 | button("Show") { 52 | setOnClickListener { 53 | 54 | val fragment = FoodListFragment( 55 | FoodListFragment.DataSource.FilterAndSort( 56 | kinds = foodChecks.filterValues { it.isChecked }.keys, 57 | sortBy = nutritionParameterSpinner.selectedItem as NutritionParameter, 58 | desc = descendingCheck.isChecked 59 | ) 60 | ) 61 | 62 | fragmentManager 63 | .replaceAndCommit(fragment) 64 | } 65 | } 66 | 67 | } 68 | 69 | }.view 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/LinkHolder.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.example 2 | 3 | import android.graphics.Color 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.Gravity 6 | import android.view.View 7 | import android.widget.FrameLayout 8 | import android.widget.ImageView 9 | import android.widget.TextView 10 | import com.squareup.picasso.Picasso 11 | import net.aquadc.advancedkotlinpatterns.common.recycler.BindingViewHolder 12 | import org.jetbrains.anko.* 13 | import org.jetbrains.anko.cardview.v7.cardView 14 | 15 | fun AnkoContext<*>.createLinkHolder(picasso: Picasso): BindingViewHolder { 16 | 17 | var imageView: ImageView? = null 18 | var titleTextView: TextView? = null 19 | var descriptionTextView: TextView? = null 20 | 21 | cardView { 22 | layoutParams = RecyclerView.LayoutParams(matchParent, wrapContent).apply { 23 | margin = dip(8) 24 | } 25 | cardElevation = dip(4).toFloat() 26 | radius = dip(8).toFloat() 27 | 28 | imageView = imageView { 29 | layoutParams = FrameLayout.LayoutParams(matchParent, wrapContent) 30 | adjustViewBounds = true 31 | maxHeight = dip(256) 32 | scaleType = ImageView.ScaleType.CENTER_CROP 33 | } 34 | 35 | verticalLayout { 36 | layoutParams = FrameLayout.LayoutParams(matchParent, wrapContent, Gravity.BOTTOM) 37 | backgroundColor = 0x88_FFFFFF.toInt() 38 | padding = dip(8) 39 | 40 | titleTextView = textView { 41 | textSize = 16f 42 | textColor = Color.BLACK 43 | }.lparams(width = matchParent) 44 | 45 | descriptionTextView = textView { 46 | }.lparams(width = matchParent) 47 | } 48 | } 49 | 50 | return LinkHolder(view, imageView!!, titleTextView!!, descriptionTextView!!, picasso) 51 | } 52 | 53 | private class LinkHolder( 54 | itemView: View, 55 | private val imageView: ImageView, 56 | private val titleTextView: TextView, 57 | private val descriptionTextView: TextView, 58 | private val picasso: Picasso 59 | ) : BindingViewHolder(itemView) { 60 | 61 | private var currentItem: Link? = null 62 | 63 | init { 64 | itemView.setOnClickListener { 65 | it.context.browse(currentItem!!.url) 66 | } 67 | } 68 | 69 | override fun bind(item: Link) { 70 | currentItem = item 71 | picasso.load(item.imageUrl).into(imageView) 72 | titleTextView.text = item.title 73 | descriptionTextView.text = item.description 74 | } 75 | 76 | override fun onRecycle() { 77 | currentItem = null 78 | picasso.cancelRequest(imageView) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/unsafe/FoodFilterAndSortChooserFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.fragments.unsafe 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import android.widget.ArrayAdapter 8 | import android.widget.CheckBox 9 | import android.widget.FrameLayout 10 | import net.aquadc.advancedkotlinpatterns.common.replaceAndCommit 11 | import net.aquadc.advancedkotlinpatterns.recycler.FoodKind 12 | import net.aquadc.advancedkotlinpatterns.recycler.NutritionParameter 13 | import org.jetbrains.anko.* 14 | import java.util.* 15 | 16 | class FoodFilterAndSortChooserFragment : Fragment() { 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 19 | 20 | verticalLayout { 21 | layoutParams = FrameLayout.LayoutParams(matchParent, wrapContent) 22 | padding = dip(16) 23 | 24 | textView("Sort by").lparams(width = matchParent) 25 | 26 | val nutritionParameterSpinner = spinner { 27 | id = 1 28 | adapter = ArrayAdapter(activity, android.R.layout.simple_list_item_1, NutritionParameter.Values).apply { 29 | setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) 30 | } 31 | } 32 | 33 | val descendingCheck = checkBox("Descending") { 34 | id = 2 35 | isChecked = true 36 | } 37 | 38 | textView("Include").lparams(width = matchParent) { 39 | topMargin = dip(16) 40 | } 41 | 42 | var lastId = 2 43 | val foodChecks = EnumMap(FoodKind::class.java) 44 | FoodKind.Values.forEach { 45 | foodChecks[it] = checkBox(it.name) { 46 | id = ++lastId 47 | isChecked = true 48 | } 49 | } 50 | 51 | button("Show") { 52 | setOnClickListener { 53 | 54 | /*val fragment = FoodListFragment().apply { 55 | arguments = Bundle(4).apply { 56 | putInt(FoodListFragment.ModeKey, FoodListFragment.FilterAndSortMode) 57 | putEnumSet(FoodListFragment.FoodKindsKey, foodChecks.filterValues { it.isChecked }.keys) 58 | putSerializable(FoodListFragment.SortByParameterKey, nutritionParameterSpinner.selectedItem as NutritionParameter) 59 | putBoolean(FoodListFragment.SortDescKey, descendingCheck.isChecked) 60 | } 61 | }*/ 62 | 63 | val fragment = FoodListFragment.filterAndSort( 64 | kinds = foodChecks.filterValues { it.isChecked }.keys, 65 | sortBy = nutritionParameterSpinner.selectedItem as NutritionParameter, 66 | desc = descendingCheck.isChecked) 67 | 68 | fragmentManager 69 | .replaceAndCommit(fragment) 70 | } 71 | } 72 | 73 | } 74 | 75 | }.view 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/coroutines/CoroutinesFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.coroutines 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.FrameLayout 9 | import android.widget.TextView 10 | import kotlinx.coroutines.experimental.* 11 | import org.jetbrains.anko.* 12 | import kotlinx.coroutines.experimental.android.UI 13 | import ru.gildor.coroutines.retrofit.await 14 | import java.util.concurrent.TimeUnit.SECONDS as Seconds // you can import `MILLISECONDS as Seconds` for testing ;) 15 | 16 | class CoroutinesFragment : Fragment() { 17 | 18 | private lateinit var textView: TextView 19 | private var teaser: Job? = null 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | retainInstance = true 24 | } 25 | 26 | override fun onCreateView(i: LayoutInflater, c: ViewGroup, savedInstanceState: Bundle?): View = UI { 27 | verticalLayout { 28 | layoutParams = FrameLayout.LayoutParams(matchParent, wrapContent) 29 | padding = dip(16) 30 | 31 | button("Don't press me.") { 32 | setOnClickListener { loadButtonPressed() } 33 | }.lparams() 34 | 35 | textView = textView { 36 | id = 1 37 | freezesText = true 38 | textSize = 20f 39 | }.lparams(width = matchParent) { 40 | horizontalMargin = dip(8) 41 | topMargin = dip(16) 42 | } 43 | 44 | } 45 | }.view 46 | 47 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { 48 | super.onViewCreated(view, savedInstanceState) 49 | if (teaser != null) return 50 | teaser = launch(UI) { 51 | textView.text = "Press the button." 52 | delay(3, Seconds) 53 | textView.text = "Hey. Press the button." 54 | delay(3, Seconds) 55 | textView.text = "Are you kidding me? Press it." 56 | delay(3, Seconds) 57 | textView.text = "Are you kidding me? Press it. Now." 58 | delay(3, Seconds) 59 | textView.text = "LOL, you're stubborn one." 60 | delay(3, Seconds) 61 | textView.text = "I can't wait. PRESS IT!" 62 | delay(3, Seconds) 63 | textView.text = "Okay, I will shut up. Please, press it." 64 | delay(5, Seconds) 65 | textView.text = "Ugh..." 66 | textView.text = try { 67 | val location = IpApi.myLocation.await() 68 | "I know, you're somewhere near to (${location.latitude}; ${location.longitude})! " + 69 | "I'm on my way to ${location.city}, ${location.country}." 70 | } catch (e: Throwable) { 71 | "Oh, I'm even unable to figure out where do you live because of $e." 72 | } 73 | } 74 | } 75 | 76 | private fun loadButtonPressed() { 77 | teaser?.cancel() 78 | textView.text = "¯\\_(ツ)_/¯" 79 | } 80 | 81 | override fun onDestroy() { 82 | teaser?.cancel() 83 | super.onDestroy() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/LoaderFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.crossinlineOneLiner 2 | 3 | import android.app.Fragment 4 | import android.app.LoaderManager 5 | import android.content.Loader 6 | import android.os.Bundle 7 | import android.text.Spannable 8 | import android.text.SpannableStringBuilder 9 | import android.text.method.LinkMovementMethod 10 | import android.view.LayoutInflater 11 | import android.view.ViewGroup 12 | import android.widget.EditText 13 | import android.widget.FrameLayout 14 | import android.widget.TextView 15 | import net.aquadc.advancedkotlinpatterns.feature.sealed.unions.either.Either 16 | import net.aquadc.advancedkotlinpatterns.feature.sealed.unions.either.let 17 | import org.jetbrains.anko.* 18 | 19 | class LoaderFragment : Fragment(), LoaderManager.LoaderCallbacks> { 20 | 21 | private lateinit var loaderTextView: TextView 22 | private lateinit var watchedEditText: EditText 23 | private lateinit var watchedTextView: TextView 24 | 25 | override fun onActivityCreated(savedInstanceState: Bundle?) { 26 | super.onActivityCreated(savedInstanceState) 27 | loaderManager.initLoader(0, null, this) 28 | } 29 | 30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 31 | 32 | verticalLayout { 33 | layoutParams = FrameLayout.LayoutParams(matchParent, matchParent) 34 | padding = dip(16) 35 | 36 | loaderTextView = textView("Loading...") { 37 | textSize = 22f 38 | movementMethod = LinkMovementMethod.getInstance() 39 | setLineSpacing(0f, 1.2f) 40 | }.lparams(width = matchParent) 41 | 42 | watchedEditText = editText { 43 | id = 1 44 | hint = "Type anything..." 45 | addTextChangedListener(AfterTextChangedListener { watchedTextView.text = it }) 46 | }.lparams(width = matchParent) { 47 | topMargin = dip(16) 48 | } 49 | 50 | watchedTextView = textView("I'm watching you!") { 51 | textSize = 14f 52 | }.lparams(width = matchParent) { 53 | topMargin = dip(16) 54 | } 55 | 56 | } 57 | 58 | }.view 59 | 60 | override fun onCreateLoader(id: Int, args: Bundle?): Loader> = when (id) { 61 | 0 -> CachedAsyncTaskLoader(activity) { 62 | Thread.sleep(3_000) 63 | SpannableStringBuilder("Content loaded! You may also ").apply { 64 | var start = length 65 | append("click here to see the power") 66 | setSpan(ClickableSpan { it.context.toast("See the power!") }, start, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) 67 | append(", or ") 68 | 69 | start = length 70 | append("tap here to feel the force") 71 | setSpan(ClickableSpan { it.context.vibrator.vibrate(500) }, start, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) 72 | append(".") 73 | } 74 | } 75 | else -> throw IndexOutOfBoundsException() 76 | } 77 | 78 | override fun onLoadFinished(loader: Loader>, data: Either) { 79 | loaderTextView.text = data.let({ it.message ?: it.toString() }, { it }) 80 | } 81 | 82 | override fun onLoaderReset(loader: Loader>) = Unit 83 | 84 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.AppExtension 2 | import com.android.build.gradle.AppPlugin 3 | import com.android.build.gradle.internal.dsl.BuildType 4 | import com.android.builder.core.DefaultApiVersion 5 | import com.android.builder.core.DefaultProductFlavor 6 | import com.android.builder.model.ApiVersion 7 | import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper 8 | 9 | apply { 10 | plugin() 11 | plugin() 12 | } 13 | 14 | android { 15 | compileSdkVersion(25) 16 | buildToolsVersion("25.0.3") 17 | defaultConfig { 18 | minSdkVersion(21) 19 | targetSdkVersion(25) 20 | 21 | applicationId = "net.aquadc.advancedkotlinpatterns" 22 | versionCode = 1 23 | versionName = "1.0" 24 | } 25 | buildTypes { 26 | getByName("debug") { 27 | isMinifyEnabled = true 28 | isShrinkResources = true 29 | proguardFiles("proguard-rules.pro") 30 | } 31 | getByName("release") { 32 | // unused, I don't want to specify a signingCongfig 33 | } 34 | } 35 | packagingOptions { 36 | exclude("META-INF/*kotlin*") 37 | exclude("META-INF/kotlin*") 38 | exclude("kotlin") 39 | exclude("kotlin/*") 40 | exclude("kotlin/**") 41 | } 42 | } 43 | 44 | dependencies { 45 | /*androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 46 | exclude group: 'com.android.support', module: 'support-annotations' 47 | }) 48 | testCompile 'junit:junit:4.12'*/ 49 | 50 | val ext = rootProject.extra 51 | 52 | val supportVersion = ext["support_version"] 53 | 54 | // recycler without ProGuard rules 55 | compile(group = "", name = "recyclerview-v7-$supportVersion-no-proguard", ext = "aar") 56 | // recycler's dependencies 57 | compile("com.android.support:support-annotations:$supportVersion") 58 | compile("com.android.support:support-compat:$supportVersion") 59 | compile("com.android.support:support-core-ui:$supportVersion") 60 | 61 | compile("com.android.support:cardview-v7:$supportVersion") 62 | 63 | compile("org.jetbrains.kotlin:kotlin-stdlib-jre7:${ext["kotlin_version"]}") 64 | 65 | val ankoVersion = ext["anko_version"] 66 | compile("org.jetbrains.anko:anko-sdk21:$ankoVersion") 67 | compile("org.jetbrains.anko:anko-sdk21-coroutines:$ankoVersion") 68 | compile("org.jetbrains.anko:anko-recyclerview-v7:$ankoVersion") 69 | compile("org.jetbrains.anko:anko-cardview-v7:$ankoVersion") 70 | 71 | compile("com.squareup.retrofit2:retrofit:2.3.0") 72 | compile("ru.gildor.coroutines:kotlin-coroutines-retrofit:0.7.1") 73 | 74 | compile("com.squareup.picasso:picasso:2.5.2") 75 | compile("com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0") 76 | } 77 | 78 | repositories { 79 | maven { url = uri("http://dl.bintray.com/kotlin/kotlin-eap-1.2") } 80 | flatDir { dirs("libs") } 81 | } 82 | 83 | fun Project.android(setup: AppExtension.() -> Unit) = the().setup() 84 | 85 | fun NamedDomainObjectContainer.release(setup: BuildType.() -> Unit) = findByName("release").setup() 86 | 87 | fun AppExtension.defaultConfigExtension(setup: DefaultProductFlavor.() -> Unit) = defaultConfig.setup() 88 | 89 | fun AppExtension.buildTypesExtension(setup: NamedDomainObjectContainer.() -> Unit) = buildTypes({ setup() }) 90 | 91 | fun DefaultProductFlavor.setMinSdkVersion(value: Int) = setMinSdkVersion(value.asApiVersion()) 92 | 93 | fun DefaultProductFlavor.setTargetSdkVersion(value: Int) = setTargetSdkVersion(value.asApiVersion()) 94 | 95 | fun Int.asApiVersion(): ApiVersion = DefaultApiVersion.create(this) -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/AttachmentsFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.sealed.example 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.support.v7.widget.LinearLayoutManager 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import net.aquadc.advancedkotlinpatterns.app 10 | import net.aquadc.advancedkotlinpatterns.common.recycler.BindingViewHolder 11 | import net.aquadc.advancedkotlinpatterns.common.recycler.ListAdapter 12 | import org.jetbrains.anko.AnkoContext 13 | import org.jetbrains.anko.UI 14 | import org.jetbrains.anko.recyclerview.v7.recyclerView 15 | 16 | class AttachmentsFragment : Fragment() { 17 | 18 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View = UI { 19 | recyclerView { 20 | id = 1 21 | layoutManager = LinearLayoutManager(activity) 22 | adapter = ListAdapter(attachments, 23 | this@AttachmentsFragment::getViewTypeOf, 24 | this@AttachmentsFragment::createViewHolder) 25 | } 26 | }.view 27 | 28 | private fun getViewTypeOf(attachment: Attachment): Int = when (attachment) { 29 | is Link -> 1 30 | is Photo -> 2 31 | is Sticker -> 3 32 | } 33 | 34 | private fun createViewHolder(actx: AnkoContext>, viewType: Int): BindingViewHolder = 35 | when (viewType) { 36 | 1 -> actx.createLinkHolder(app.picasso) 37 | 2 -> actx.createPhotoHolder(app.picasso) 38 | 3 -> actx.createStickerHolder(app.picasso) 39 | else -> throw IndexOutOfBoundsException() 40 | } 41 | 42 | private companion object { 43 | private val attachments = listOf( 44 | Link( 45 | "https://developer.android.com/", 46 | "Android Developers", 47 | "Much useful information about bound services, content providers, AsyncTasks, SQLite cursors, or whatever you love.", 48 | "https://pbs.twimg.com/profile_images/606585229034135553/2NqZJYQI.png"), 49 | Link( 50 | "https://github.com/", 51 | "GitHub", 52 | "GitHub is a development platform inspired by the way you work.", 53 | "https://assets-cdn.github.com/images/modules/logos_page/Octocat.png" 54 | ), 55 | Link( 56 | "https://f-droid.org/", 57 | "F-Droid", 58 | "F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform.", 59 | "http://img.over-blog-kiwi.com/0/71/05/10/20161028/ob_db889d_f-droid.png" 60 | ), 61 | Photo( 62 | "I'll eat these as easily as I eat RAM, you know.", 63 | "http://fscl01.fonpit.de/userfiles/6727621/image/2016/ANDROID-M-N-O-P/AndroidPIT-android-O-Oreo-2065.jpg" 64 | ), 65 | Photo( 66 | "Nexus 5X (codenamed bullhead) is an Android smartphone manufactured by LG Electronics, co-developed with and marketed by Google Inc. as part of its Nexus line of flagship devices.", 67 | "https://www.google.com/nexus/images/nexus5x/5x-gallery-thumb-01.jpg" 68 | ), 69 | Sticker( 70 | "http://www.unixstickers.com/image/cache/data/stickers/android/android_shaped.sh-600x600.png" 71 | ), 72 | Sticker( 73 | "http://devstickers.com/assets/img/pro/4mh6.png" 74 | ), 75 | Sticker( 76 | "https://maxcdn.icons8.com/Share/icon/Operating_Systems//linux1600.png" 77 | ) 78 | ) 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Advanced Kotlin Patterns on Android / Idiomatic Kotlin code on Android 2 | 3 | #### Presentations 4 | * [Safe fragments](https://speakerdeck.com/gdg_rnd/mikhail-goriunov-advanced-kotlin-patterns-on-android-safe-fragments) presentation 5 | * [Sealed classes](https://speakerdeck.com/gdg_rnd/mikhail-goriunov-advanced-kotlin-patterns-on-android-sealed-classes) presentation 6 | 7 | #### Code 8 | 9 | * A [fragment made with Anko](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/ankoRecyclerView/AnkoRecyclerViewFragment.kt) 10 | with a RecyclerView, its 11 | [ViewHolder](/app/src/main/java/net/aquadc/advancedkotlinpatterns/recycler/FoodItemHolder.kt) and 12 | [item view](/app/src/main/java/net/aquadc/advancedkotlinpatterns/recycler/createFoodItemHolder.kt) 13 | 14 | * Crossinline one-liner anonymous classes 15 | * [parcelableCreator](/app/src/main/java/net/aquadc/advancedkotlinpatterns/common/parcelable.kt#L8) function, call-sites: 16 | * [FoodListFragment.DataSource.Popular](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/FoodListFragment.kt#L69) (parcelable singleton) 17 | * [FoodListFragment.DataSource.FilterAndSort](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/FoodListFragment.kt#L79) 18 | * [User](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/User.kt) 19 | * [CachedAsyncTaskLoader](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/CachedAsyncTaskLoader.kt), 20 | [call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/LoaderFragment.kt#L59) 21 | * [ClickableSpan](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/ClickableSpan.kt), 22 | [call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/LoaderFragment.kt#L64) 23 | * [TextWatcher](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/TextWatcher.kt), 24 | [call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/crossinlineOneLiner/LoaderFragment.kt#L43) 25 | 26 | * Reactive data binding via broadcasts 27 | * [bind() implementation](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/bind.kt) 28 | * [ProfileFragment](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/ProfileFragment.kt#L29) — usage sample 29 | 30 | * Safe fragments 31 | * [Unsafe fragment implementation](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/unsafe/FoodListFragment.kt), 32 | [Unsafe call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/unsafe/FoodFilterAndSortChooserFragment.kt#L54) 33 | * [Safe fragment](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/FoodListFragment.kt) 34 | with deprecated no-arg constructor, normal constructor, and mode delegation; 35 | [call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/FoodFilterAndSortChooserFragment.kt#L54) 36 | 37 | * Coroutines 38 | [Sample use](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/coroutines/CoroutinesFragment.kt#L50), 39 | including `delay` and bindings to Retrofit. 40 | 41 | * Sealed classes 42 | * [Object-oriented solution](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/javaReflectTypeFantasy/Type.java) 43 | (contrary to original java.lang.reflect.Type), 44 | [call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/javaReflectTypeFantasy/TypeCallSite.java) 45 | * [Functional-style solution](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/kotlinReflectTypeStyle/Type.kt), 46 | [call-site](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/kotlinReflectTypeStyle/CallSite.kt) 47 | * [Option](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/unions/option/Option.kt), 48 | [Either](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/unions/either/Either.kt) 49 | * [sealed class Attachment](app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/Attachment.kt), 50 | [AttachmentsFragment](/app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/sealed/example/AttachmentsFragment.kt) use-site 51 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/safe/FoodListFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.fragments.safe 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.os.Parcel 6 | import android.os.Parcelable 7 | import android.support.v7.widget.DividerItemDecoration 8 | import android.support.v7.widget.DividerItemDecoration.VERTICAL 9 | import android.support.v7.widget.LinearLayoutManager 10 | import android.view.LayoutInflater 11 | import android.view.ViewGroup 12 | import net.aquadc.advancedkotlinpatterns.app 13 | import net.aquadc.advancedkotlinpatterns.common.* 14 | import net.aquadc.advancedkotlinpatterns.common.recycler.ListAdapter 15 | import net.aquadc.advancedkotlinpatterns.feature.fragments.getFilteredAndSortedFoodItems 16 | import net.aquadc.advancedkotlinpatterns.feature.fragments.getSortedByPopularityFoodItems 17 | import net.aquadc.advancedkotlinpatterns.feature.fragments.safe.FoodListFragment.DataSource 18 | import net.aquadc.advancedkotlinpatterns.recycler.FoodItem 19 | import net.aquadc.advancedkotlinpatterns.recycler.FoodKind 20 | import net.aquadc.advancedkotlinpatterns.recycler.NutritionParameter 21 | import net.aquadc.advancedkotlinpatterns.recycler.createFoodItemHolder 22 | import org.jetbrains.anko.UI 23 | import org.jetbrains.anko.recyclerview.v7.recyclerView 24 | 25 | /** 26 | * Shows a list of food according to the specified [DataSource]. 27 | */ 28 | class FoodListFragment : Fragment { 29 | 30 | @Deprecated(message = "use FoodListFragment(DataSource) instead", level = DeprecationLevel.ERROR) 31 | constructor() 32 | 33 | @Deprecated(message = "use FoodListFragment(DataSource) instead", level = DeprecationLevel.ERROR) 34 | override fun setArguments(args: Bundle) { 35 | if (arguments != null) 36 | throw IllegalStateException("arguments were already set to $arguments, was attempt to replace with $args") 37 | super.setArguments(args) 38 | } 39 | 40 | constructor(dataSource: DataSource) { 41 | super.setArguments(Bundle(1).apply { 42 | putParcelable(DataSourceKey, dataSource) 43 | }) 44 | } 45 | 46 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 47 | 48 | val source = arguments.getParcelable(DataSourceKey) 49 | 50 | recyclerView { 51 | id = 1 52 | layoutManager = LinearLayoutManager(activity) 53 | adapter = ListAdapter(source.data) { createFoodItemHolder(app.picasso) } 54 | addItemDecoration(DividerItemDecoration(activity, VERTICAL)) 55 | } 56 | 57 | }.view 58 | 59 | private companion object { 60 | private const val DataSourceKey = "dataSource" 61 | } 62 | 63 | interface DataSource : Parcelable { 64 | val data: List 65 | 66 | /** 67 | * Provides a list of food, sorted by descending of popularity (most popular first) 68 | */ 69 | object Popular : DataSource, BoringParcelable { 70 | override val data: List get() = getSortedByPopularityFoodItems() 71 | 72 | override fun writeToParcel(dest: Parcel, flags: Int) = Unit 73 | @JvmField val CREATOR = parcelableCreator { Popular } 74 | } 75 | 76 | /** 77 | * Provides a list of food of certain [FoodKind]s sorted by specified [NutritionParameter] 78 | */ 79 | class FilterAndSort( 80 | private val kinds: Set, 81 | private val sortBy: NutritionParameter, 82 | private val desc: Boolean 83 | ) : DataSource, BoringParcelable { 84 | override val data: List get() = getFilteredAndSortedFoodItems(kinds, sortBy, desc) 85 | 86 | override fun writeToParcel(dest: Parcel, flags: Int) { 87 | dest.writeEnumSet(kinds) 88 | dest.writeEnum(sortBy) 89 | dest.writeBoolean(desc) 90 | } 91 | companion object { 92 | @JvmField val CREATOR = parcelableCreator { FilterAndSort( 93 | kinds = it.readEnumSet(), 94 | sortBy = it.readEnum(), 95 | desc = it.readBoolean() 96 | ) } 97 | } 98 | } 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/recycler/items.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.recycler 2 | 3 | import java.util.Collections.unmodifiableList 4 | 5 | val foodItems: List = unmodifiableList(listOf( 6 | FoodItem( 7 | "https://pbs.twimg.com/profile_images/726613174523297792/v0edN9KP_400x400.jpg", 8 | "Falafel", 9 | "a deep-fried ball, doughnut or patty made from ground chickpeas, fava beans, or both. " + 10 | "Falafel is a traditional Middle Eastern food, commonly served in a pita, " + 11 | "which acts as a pocket, or wrapped in a flatbread known as taboon; " + 12 | "\"falafel\" also frequently refers to a wrapped sandwich that is prepared in this way.", 13 | NutritionInfo(energy = 333.0, fats = 18.0, carbohydrates = 32.0, proteins = 13.0), 14 | FoodKind.Meal, 15 | 31_300_000 16 | ), 17 | FoodItem( 18 | "https://bigoven-res.cloudinary.com/image/upload/t_recipe-256/tofu-baked-in-a-lemon-rosemary-43d3fe.jpg", 19 | "Tofu", 20 | "a food cultivated by coagulating soy milk and then pressing the resulting curds " + 21 | "into soft white blocks. " + 22 | "It is a component in East Asian, Southeast Asian and West African cuisines.", 23 | NutritionInfo(energy = 76.0, fats = 4.8, carbohydrates = 1.9, proteins = 8.0), 24 | FoodKind.Meal, 25 | 72_100_000 26 | ), 27 | FoodItem( 28 | "https://static.wixstatic.com/media/f012eb_646ac521ce864eb8bd2d13477870f2dd~mv2.png/v1/fill/w_256,h_256,al_c,q_90/file.jpg", 29 | "Cellophane noodles", 30 | "a type of transparent noodle made from starch " + 31 | "(such as mung bean starch, yam, potato starch, cassava, canna or batata starch) and water.", 32 | NutritionInfo(energy = 351.0, fats = .1, carbohydrates = 86.0, proteins = .2), 33 | FoodKind.Meal, 34 | 1_500_000 35 | ), 36 | FoodItem( 37 | "https://s-media-cache-ak0.pinimg.com/736x/94/4d/47/944d47afe47ea473d19264dd83ac0235--china-food-tofu-recipes.jpg", 38 | "Tofu skin", 39 | "a food product made from soybeans. During the boiling of soy milk, in an open shallow pan, " + 40 | "a film or skin forms on the liquid surface.", 41 | NutritionInfo(energy = 387.0, fats = 19.0, carbohydrates = 12.0, proteins = 42.0), 42 | FoodKind.Meal, 43 | 11_800_000 44 | ), 45 | FoodItem( 46 | "https://upload.wikimedia.org/wikipedia/commons/f/f8/Dal_Makhani.jpg", 47 | "Dal moong", // it has nothing in common with Data Access Layer 48 | "various soups prepared from dried, split pulses.", 49 | NutritionInfo(energy = 347.0, fats = 1.0, carbohydrates = 63.0, proteins = 24.0), 50 | FoodKind.Meal, 51 | 818_000 52 | ), 53 | FoodItem( 54 | "http://static.wixstatic.com/media/92eca4_234f5123236c4e2f9032ef39e851d28f.jpg_256", 55 | "Green tea", 56 | "is a type of tea that is made from Camellia sinensis leaves " + 57 | "that have not undergone the same withering and oxidation process " + 58 | "used to make oolong and black tea. " + 59 | "Green tea originated in China, but its production has spread to many countries in Asia.", 60 | NutritionInfo(energy = 2.0, fats = .0, carbohydrates = .47, proteins = .0), 61 | FoodKind.Drink, 62 | 92_200_000 63 | ), 64 | FoodItem( 65 | "http://www.rivertea.com/blog/wp-content/uploads/2013/01/black-tea-cup-e1359634422907.jpg", 66 | "Black tea", 67 | "is a type of tea that is more oxidized than oolong, green and white teas. " + 68 | "Black tea is generally stronger in flavor than the less oxidized teas.", 69 | NutritionInfo(energy = 1.1, fats = .0, carbohydrates = .3, proteins = .0), 70 | FoodKind.Drink, 71 | 39_000_000 72 | ), 73 | FoodItem( 74 | "https://meileaf.com/uploaded/thumbnails/db_file_img_397_365x365_eaeaec.jpg", 75 | "Oolong", 76 | "is a traditional Chinese tea (Camellia sinensis) " + 77 | "produced through a process including withering the plant under strong sun " + 78 | "and oxidation before curling and twisting.", 79 | NutritionInfo(energy = 1.4, fats = .051, carbohydrates = .04, proteins = .2), 80 | FoodKind.Drink, 81 | 14_000_000 82 | ) 83 | )) 84 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/fragments/unsafe/FoodListFragment.kt: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.fragments.unsafe 2 | 3 | import android.app.Fragment 4 | import android.os.Bundle 5 | import android.support.v7.widget.DividerItemDecoration 6 | import android.support.v7.widget.LinearLayoutManager 7 | import android.view.LayoutInflater 8 | import android.view.ViewGroup 9 | import net.aquadc.advancedkotlinpatterns.app 10 | import net.aquadc.advancedkotlinpatterns.common.getEnumSet 11 | import net.aquadc.advancedkotlinpatterns.common.putEnumSet 12 | import net.aquadc.advancedkotlinpatterns.common.recycler.ListAdapter 13 | import net.aquadc.advancedkotlinpatterns.feature.fragments.getFilteredAndSortedFoodItems 14 | import net.aquadc.advancedkotlinpatterns.feature.fragments.getSortedByPopularityFoodItems 15 | import net.aquadc.advancedkotlinpatterns.recycler.FoodKind 16 | import net.aquadc.advancedkotlinpatterns.recycler.NutritionParameter 17 | import net.aquadc.advancedkotlinpatterns.recycler.createFoodItemHolder 18 | import org.jetbrains.anko.UI 19 | import org.jetbrains.anko.recyclerview.v7.recyclerView 20 | 21 | /** 22 | * Shows a list of food. 23 | * Popular mode: 24 | * shows a list of food, sorted by descending of popularity (most popular first) 25 | * FilterAndSort: 26 | * shows a list of food of certain [FoodKind]s sorted by specified [NutritionParameter] 27 | */ 28 | class FoodListFragment : Fragment() { 29 | 30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?) = UI { 31 | 32 | val data = when (arguments.getInt(ModeKey)) { 33 | PopularMode -> 34 | getSortedByPopularityFoodItems() 35 | 36 | FilterAndSortMode -> 37 | getFilteredAndSortedFoodItems( 38 | kinds = arguments.getEnumSet(FoodKindsKey), 39 | sortBy = arguments.getSerializable(SortByParameterKey) as NutritionParameter, 40 | desc = arguments.getBoolean(SortDescKey)) 41 | 42 | else -> 43 | throw IndexOutOfBoundsException() 44 | } 45 | 46 | recyclerView { 47 | id = 1 48 | layoutManager = LinearLayoutManager(activity) 49 | adapter = ListAdapter(data) { createFoodItemHolder(app.picasso) } 50 | addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL)) 51 | } 52 | 53 | }.view 54 | 55 | /* 56 | /** 57 | *

This companion describes the most unsafe way of passing arguments: 58 | * all constants are public, value types are not documented at all. 59 | * Just create a fragment, instantiate a Bundle for arguments, 60 | * fill it (if you guess how) and watch it burns.

61 | * 62 | *

Call-site example for [PopularMode]

63 | *
FoodListFragment().apply {
 64 |      *     arguments = Bundle(1).apply {
 65 |      *         putInt(FoodListFragment.ModeKey, FoodListFragment.PopularMode)
 66 |      *     }
 67 |      * }
68 | * 69 | *

Call-site example for [FilterAndSortMode]

70 | *
FoodListFragment().apply {
 71 |      *     arguments = Bundle(4).apply {
 72 |      *         putInt(FoodListFragment.ModeKey, FoodListFragment.FilterAndSortMode)
 73 |      *         putEnumSet(FoodListFragment.FoodKindsKey, foodChecks.filterValues { it.isChecked }.keys)
 74 |      *         putSerializable(FoodListFragment.SortByParameterKey, nutritionParameterSpinner.selectedItem as NutritionParameter)
 75 |      *         putBoolean(FoodListFragment.SortDescKey, descendingCheck.isChecked)
 76 |      *     }
 77 |      * }
78 | */ 79 | companion object { 80 | // @formatter:off 81 | const val ModeKey = "mode" 82 | const val PopularMode = 1 83 | const val FilterAndSortMode = 2 84 | const val FoodKindsKey = "food kinds" 85 | const val SortByParameterKey = "sort by" 86 | const val SortDescKey = "desc" 87 | // @formatter:on 88 | } 89 | */ 90 | 91 | /** 92 | *

This Companion describes a safer way to declare arguments contract: 93 | * all constants are private and used by factories, 94 | * and call-site just assumes that factories are valid & correct.

95 | * 96 | *

Call-site example for [popular] mode

97 | *

`FoodListFragment.popular()` 98 | * (method reference: `FoodListFragment.Companion::popular`)

99 | * 100 | *

Call-site example for [filterAndSort] mode

101 | *
FoodListFragment.filterAndSort(
102 |      *         kinds = foodChecks.filterValues { it.isChecked }.keys,
103 |      *         sortBy = nutritionParameterSpinner.selectedItem as NutritionParameter,
104 |      *         desc = descendingCheck.isChecked)
105 | */ 106 | companion object { 107 | // @formatter:off 108 | private const val ModeKey = "mode" 109 | private const val PopularMode = 1 110 | private const val FilterAndSortMode = 2 111 | private const val FoodKindsKey = "food kinds" 112 | private const val SortByParameterKey = "sort by" 113 | private const val SortDescKey = "desc" 114 | // @formatter:on 115 | 116 | fun popular() = FoodListFragment().apply { 117 | arguments = Bundle(1).apply { 118 | putInt(ModeKey, PopularMode) 119 | } 120 | } 121 | 122 | fun filterAndSort(kinds: Set, sortBy: NutritionParameter, desc: Boolean) = 123 | FoodListFragment().apply { 124 | arguments = Bundle(4).apply { 125 | putInt(ModeKey, FilterAndSortMode) 126 | putEnumSet(FoodKindsKey, kinds) 127 | putSerializable(SortByParameterKey, sortBy) 128 | putBoolean(SortDescKey, desc) 129 | } 130 | } 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /app/src/main/java/net/aquadc/advancedkotlinpatterns/feature/bind/LocalBroadcastManager.java: -------------------------------------------------------------------------------- 1 | package net.aquadc.advancedkotlinpatterns.feature.bind; 2 | 3 | /* 4 | * Copyright (C) 2011 The Android Open Source Project 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.IntentFilter; 23 | import android.net.Uri; 24 | import android.os.Handler; 25 | import android.os.Message; 26 | import android.util.Log; 27 | 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.Set; 31 | 32 | /** 33 | * Helper to register for and send broadcasts of Intents to local objects 34 | * within your process. This has a number of advantages over sending 35 | * global broadcasts with {@link android.content.Context#sendBroadcast}: 36 | *
    37 | *
  • You know that the data you are broadcasting won't leave your app, so 38 | * don't need to worry about leaking private data. 39 | *
  • It is not possible for other applications to send these broadcasts to 40 | * your app, so you don't need to worry about having security holes they can 41 | * exploit. 42 | *
  • It is more efficient than sending a global broadcast through the 43 | * system. 44 | *
45 | */ 46 | public final class LocalBroadcastManager { 47 | private static class ReceiverRecord { 48 | final IntentFilter filter; 49 | final BroadcastReceiver receiver; 50 | boolean broadcasting; 51 | 52 | ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) { 53 | filter = _filter; 54 | receiver = _receiver; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "Receiver{" + 60 | receiver + 61 | " filter=" + 62 | filter + 63 | "}"; 64 | } 65 | } 66 | 67 | private static class BroadcastRecord { 68 | final Intent intent; 69 | final ArrayList receivers; 70 | 71 | BroadcastRecord(Intent _intent, ArrayList _receivers) { 72 | intent = _intent; 73 | receivers = _receivers; 74 | } 75 | } 76 | 77 | private static final String TAG = "LocalBroadcastManager"; 78 | private static final boolean DEBUG = false; 79 | 80 | private final Context mAppContext; 81 | 82 | private final HashMap> mReceivers = new HashMap<>(); 83 | private final HashMap> mActions = new HashMap<>(); 84 | 85 | private final ArrayList mPendingBroadcasts = new ArrayList<>(); 86 | 87 | static final int MSG_EXEC_PENDING_BROADCASTS = 1; 88 | 89 | private final Handler mHandler; 90 | 91 | private static final Object mLock = new Object(); 92 | private static LocalBroadcastManager mInstance; 93 | 94 | public static LocalBroadcastManager getInstance(Context context) { 95 | synchronized (mLock) { 96 | if (mInstance == null) { 97 | mInstance = new LocalBroadcastManager(context.getApplicationContext()); 98 | } 99 | return mInstance; 100 | } 101 | } 102 | 103 | private LocalBroadcastManager(Context context) { 104 | mAppContext = context; 105 | mHandler = new Handler(context.getMainLooper()) { 106 | 107 | @Override 108 | public void handleMessage(Message msg) { 109 | switch (msg.what) { 110 | case MSG_EXEC_PENDING_BROADCASTS: 111 | executePendingBroadcasts(); 112 | break; 113 | default: 114 | super.handleMessage(msg); 115 | } 116 | } 117 | }; 118 | } 119 | 120 | /** 121 | * Register a receive for any local broadcasts that match the given IntentFilter. 122 | * 123 | * @param receiver The BroadcastReceiver to handle the broadcast. 124 | * @param filter Selects the Intent broadcasts to be received. 125 | * 126 | * @see #unregisterReceiver 127 | */ 128 | public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 129 | synchronized (mReceivers) { 130 | ReceiverRecord entry = new ReceiverRecord(filter, receiver); 131 | ArrayList filters = mReceivers.get(receiver); 132 | if (filters == null) { 133 | filters = new ArrayList<>(1); 134 | mReceivers.put(receiver, filters); 135 | } 136 | filters.add(filter); 137 | for (int i=0; i entries = mActions.get(action); 140 | if (entries == null) { 141 | entries = new ArrayList<>(1); 142 | mActions.put(action, entries); 143 | } 144 | entries.add(entry); 145 | } 146 | } 147 | } 148 | 149 | /** 150 | * Unregister a previously registered BroadcastReceiver. All 151 | * filters that have been registered for this BroadcastReceiver will be 152 | * removed. 153 | * 154 | * @param receiver The BroadcastReceiver to unregister. 155 | * 156 | * @see #registerReceiver 157 | */ 158 | public void unregisterReceiver(BroadcastReceiver receiver) { 159 | synchronized (mReceivers) { 160 | ArrayList filters = mReceivers.remove(receiver); 161 | if (filters == null) { 162 | return; 163 | } 164 | for (int i=0; i receivers = mActions.get(action); 169 | if (receivers != null) { 170 | for (int k=0; k categories = intent.getCategories(); 203 | 204 | final boolean debug = DEBUG || 205 | ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); 206 | if (debug) Log.v( 207 | TAG, "Resolving type " + type + " scheme " + scheme 208 | + " of intent " + intent); 209 | 210 | ArrayList entries = mActions.get(intent.getAction()); 211 | if (entries != null) { 212 | if (debug) Log.v(TAG, "Action list: " + entries); 213 | 214 | ArrayList receivers = null; 215 | for (int i=0; i= 0) { 229 | if (debug) Log.v(TAG, " Filter matched! match=0x" + 230 | Integer.toHexString(match)); 231 | if (receivers == null) { 232 | receivers = new ArrayList<>(); 233 | } 234 | receivers.add(receiver); 235 | receiver.broadcasting = true; 236 | } else { 237 | if (debug) { 238 | String reason; 239 | switch (match) { 240 | case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; 241 | case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; 242 | case IntentFilter.NO_MATCH_DATA: reason = "data"; break; 243 | case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; 244 | default: reason = "unknown reason"; break; 245 | } 246 | Log.v(TAG, " Filter did not match: " + reason); 247 | } 248 | } 249 | } 250 | 251 | if (receivers != null) { 252 | for (int i=0; i