Marcin Moskała
Gamekit, Señor Android Developer
https://www.pinterest.de/explore/alter-computer/
interface Player {
fun playGame()
}
class RpgGamePlayer(val enemy: String) : Player {
override fun playGame() {
println("Killing $enemy")
}
}
class WitcherPlayer(enemy: String) : Player {
val player = RpgGamePlayer(enemy)
override fun playGame() {
player.playGame()
}
}
RpgGamePlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer class is delegating methods defined in Player interface to an instance of type RpgGamePlayer
interface Player {
fun playGame()
}
class RpgGamePlayer(val enemy: String) : Player {
override fun playGame() {
println("Killing $enemy")
}
}
class WitcherPlayer(enemy: String) : RpgGamePlayer(enemy)
RpgGamePlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer("monsters").playGame() // Prints: Killing monsters
public class CountingHashSet extends HashSet {
private int addCount = 0;
@Override
public boolean add(Object o) {
addCount += 1;
return super.add(o);
}
@Override
public boolean addAll(Collection collection) {
addCount += collection.size();
return super.addAll(collection);
}
public int getAddCount() {
return addCount;
}
}
// Usage
CountingHashMap s = new CountingHashMap();
s.addAll(Arrays.asList(new String[]{"A", "B", "C"}));
System.out.println(s.getAddCount()); // Prints: 6
When classes is final, private or not designed for inheritance.
When there is no "is-a" relationship
When we need only some functionalities from class
interface Player {
fun playGame()
}
class RpgGamePlayer(val enemy: String) : Player {
override fun playGame() {
println("Killing $enemy")
}
}
class WitcherPlayer(enemy: String) : Player by RpgGamePlayer(enemy)
RpgGamePlayer("monsters").playGame() // Prints: Killing monsters
WitcherPlayer("monsters").playGame() // Prints: Killing monsters
Using by keyword, we are informing compiler to delegate all methods defined in Player interface from WitcherPlayer to RpgGamePlayer
class WitcherPlayer() : Player by RpgGamePlayer("monsters")
class WitcherPlayer(enemy: String) : Player by RpgGamePlayer(enemy)
class WitcherPlayer(player: Player) : Player by player
class WitcherPlayer(val player: Player = RpgGamePlayer("monsters")) : Player by player
val player = RpgGamePlayer(10)
class WitcherPlayer() : Player by player
InputStream fis = new FileInputStream("/someFile.gz");
InputStream bis = new BufferedInputStream(fis);
InputStream gis = new ZipInputStream(bis);
ObjectInputStream ois = new ObjectInputStream(gis);
SomeObject someObject = (SomeObject) ois.readObject();
class PlayHard(player: Player) : Player by player {
override fun playGame() {
println("Arrgggh! ")
player.playGame()
}
}
// Usage
val player = WitcherPlayer("monsters")
player.playGame() // Prints: Killing monsters
val hardcore = PlayHard(player)
hardcore.playGame() // Prints: Arrgggh! Killing monsters
class User(val name: String, val surname: String)
var user: User by UserDelegate()
// Usage
println(user.name)
user = User(“Marcin”,”Moskala”)
Property user is delegated to object of type UserDelegate.
public class MainActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
// ...
}
public class MainPresenter implements Presenter {
@Inject NetworkApi networkApi;
@Inject UserService userService;
// ...
}
@Entity
@Table(name = "EMPLOYEE")
public class Employee {
@Id @GeneratedValue
@Column(name = "id")
private int id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "salary")
private int salary;
}
private UserRepository userRepo;
public UserRepository getUserRepo() {
if(userRepo == null) {
userRepo = new UserRepository();
}
return userRepo;
}
private List<String> observableList;
public void setObservableList(List<String> observableList) {
this.observableList = observableList;
onObservableListChanged()
}
Field
Property
// Java
public String description;
// Kotlin
var description: String? = null
Property
Property
// Java
private String description;
String getDescription() {
return description;
}
void setDescription(String description) {
this.description = description;
}
// Kotlin
var description: String? = null
Property
Property
// Java
private String description;
String getDescription() {
return Strings.trim(description);
}
void setDescription(String description) {
if(Strings.isNotNullOrEmpty(description))
this.description = description;
}
// Kotlin
var description: String? = null
get() = field?.trim()
set(value) {
if (value != null && value.isNotEmpty())
field = value
}
Property
Property
// Java
String getDescription() {
return "Super presentation";
}
// Kotlin
val description: String? = null
get() = "Super presentation"
class User(val name: String, val surname: String)
var user: User by UserDelegate()
// Usage
println(user.name)
user = User(“Marcin”,”Moskala”)
Property user is delegated to object of type UserDelegate.
var p$delegate = UserDelegate()
var user: User
get() = p$delegate.getValue(this, this::user)
set(value) {
p$delegate.setValue(this, this::user, value)
}
data class User(val name: String, val surname: String)
class UserDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): User
= User("Marcin", "Moskala")
operator fun setValue(thisRef: Any?, property: KProperty<*>, user: User) {
println("I am not setting new user")
}
}
fun main(args: Array<String>) {
var user: User by UserDelegate()
println(user) // Prints: User(name=Marcin, surname=Moskala)
user = User("Michal", "Michalski") // Prints: I am not setting new user
}
val someProperty by object {
operator fun getValue(thisRef: Any?, property: KProperty<*>) = "Something"
}
println(someProperty) // Prints: Something
var user$delegate = UserDelegate()
var user: User
get() = user$delegate.getValue(this, this::user)
set(value) {
user$delegate.setValue(this, this::user, value)
}
public interface ReadOnlyProperty<in R, out T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
}
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Specify allowed location
Specify allowed
type
var a by SomeDelegate() // Top-level property with delegate
fun someTopLevelFun() {
var b by SomeDelegate() // Local variable (inside top-level function) with delegate
}
class SomeClass() {
var c by SomeDelegate() // Member property with delegate
fun someMethod() {
val d by SomeDelegate() // Local variable (inside method) with delegate
}
}
var name: String by Delegates.notNull()
private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException(
"Property ${property.name} should be initialized before get."
)
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
name = "Marcin"
println(name) // Prints: Marcin
name = "Maja"
println(name) // Prints: Maja
println(name) // Error:
// Property name should be
// initialized before get.
var user: User by Delegates.notNull()
lateinit var user: User
Can be used everywhere
Can be used to all types
More efficient
Cannot be used at top level
Cannot be used to primitive (Int, Long, Double etc.)
val userRepo by lazy { UserRepository() }
private var _userRepo: UserRepository? = null
private val userRepoLock = Any()
val userRepo: UserRepository
get() {
synchronized(userRepoLock) {
if (_userRepo == null) {
_userRepo = UserRepository()
}
return _userRepo!!
}
}
val userRepo by lazy(LazyThreadSafetyMode.NONE) { UserRepository() }
class MainActivity : Activity() {
lateinit var questionLabelView: TextView
lateinit var answerLabelView: EditText
lateinit var confirmButtonView: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
questionLabelView = findViewById(R.id.main_question_label) as TextView
answerLabelView = findViewById(R.id.main_answer_label) as EditText
confirmButtonView = findViewById(R.id.main_button_confirm) as Button
}
}
class MainActivity : Activity() {
var questionLabelView: TextView by lazy { findViewById(R.id.main_question_label) }
var answerLabelView: TextView by lazy { findViewById(R.id.main_answer_label) }
var confirmButtonView: Button by lazy { findViewById(R.id.main_button_confirm) }
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
}
}
class MainActivity : Activity() {
var questionLabelView: TextView by bindView(R.id.main_question_label)
var answerLabelView: TextView by bindView(R.id.main_answer_label)
var confirmButtonView: Button by bindView(R.id.main_button_confirm)
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
}
}
fun <T: View> Activity.bindView(viewId: Int) = lazy { findViewById<T>(viewId) }
class SettingsActivity : Activity() {
private val titleString by bindString(R.string.title)
private val blueColor by bindColor(R.color.blue)
private val doctor by extra<Doctor>(DOCTOR_KEY)
private val title by extraString(TITLE_KEY)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
}
}
fun <T> Activity.bindString(@IdRes id: Int): Lazy<T> = lazy { getString(id) }
fun <T> Activity.bindColour(@IdRes id: Int): Lazy<T> = lazy { getColour(id) }
fun <T : Parcelable> Activity.extra(key: String) = lazy { intent.extras.getParcelable<T>(key) }
fun Activity.extraString(key: String) = lazy { intent.extras.getString(key) }
class MainActivity : Activity(), MainView {
private val doctor by extra<Doctor>(DOCTOR_KEY)
private val title by extraString(TITLE_KEY)
private val titleView by bindView<TextView>(R.string.title_view)
private val presenter by lazy { MainPresenter(this, doctor) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
titleView.text = title
}
}
var name: String by Delegates.observable("Empty"){ property, oldValue, newValue ->
println("$oldValue -> $newValue")
}
// Usage
name = "Martin" // Prints: Empty -> Martin
name = "Igor" // Prints: Martin -> Igor
name = "Igor" // Prints: Igor -> Igor
var list: List<LocalDate> by observable(list) { _, old, new ->
if(new != old) notifyDataSetChanged()
}
private var drawerOpen by Delegates.observable(false) { _, _, opened ->
if (opened) drawerLayout.openDrawer(GravityCompat.START)
else drawerLayout.closeDrawer(GravityCompat.START)
}
var presenters: List<Presenter> by observable(list) { _, old, new ->
(old - new).forEach { it.onDestroy() }
(new - old).forEach { it.onCreate() }
}
var list: List<String> by Delegates.vetoable(emptyList()) { prop, old, new ->
if(new.size < 3) return@vetoable false
println(new)
true
}
// Usage
listVetoable = listOf("A", "B", "C") // Update A, B, C
println(listVetoable) // Prints: [A, B, C]
listVetoable = listOf("A") // Nothing happens
println(listVetoable) // Prints: [A, B, C]
listVetoable = listOf("A", "B", "C", "D", "E") // Prints: [A, B, C, D, E]
var name: String by Delegates.vetoable("") { prop, old, new ->
if (isValid(new)) {
showNewName(new)
true
} else {
showNameError()
false
}
}
class User(map: Map<String, Any>) {
val name: String by map
val kotlinProgrammer: Boolean by map
}
// Usage
val map: Map<String, Any> = mapOf(
"name" to "Marcin",
"kotlinProgrammer" to true
)
val user = User(map)
println(user.name) // Prints: Marcin
println(user.kotlinProgrammer) // Prints: true
operator fun <V, V1: V> Map<String, V>.getValue(
thisRef: Any?,
property: KProperty<*>
): V1 {
val key = property.name
val value = get(key)
if (value == null && !containsKey(key)) {
throw NoSuchElementException("Key ${property.name} is missing in the map.")
} else {
return value as V1
}
}
class User(val map: MutableMap<String, Any>) {
var name: String by map
}
// Usage
val map = mutableMapOf("name" to "Marcin")
val user = User(map)
println(user.name) // Prints: Marcin
user.map.put("name", "Igor")
println(user.name) // Prints: Igor
user.name = "Michal"
println(user.name) // Prints: Michal
fun <T> mutableLazy(initializer: () -> T): ReadWriteProperty<Any?, T>
= MutableLazy<T>(initializer)
private class MutableLazy<T>(val initializer: () -> T) : ReadWriteProperty<Any?, T> {
private var value: T? = null
private var initialized = false
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
synchronized(this) {
if (!initialized) {
value = initializer()
initialized = true
}
return value as T
}
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
synchronized(this) {
this.value = value
initialized = true
}
}
}
var gameMode : GameMode by mutableLazy {
getDefaultGameMode()
}
var mapConfiguration : MapConfiguration by mutableLazy {
getSavedMapConfiguration()
}
var screenResolution : ScreenResolution by mutableLazy {
getOptimalScreenResolutionForDevice()
}
public class MainActivity: BaseActivity() {
@get:Arg var userData: String by argExtra()
}
Source: spindrift-racing.com/jules-verne/drupal/en/log-book/icebergs-en
interface LoginView {
fun showProgress(show: Boolean)
fun getEmail(): String
fun setEmail(text: String)
fun getPassword(): String
fun setPassword(text: String)
fun setEmailError(id: Int?)
fun setPasswordError(id: Int?)
fun requestEmailFocus()
}
override fun getEmail(): String = emailView.text.toString()
override fun setEmail(text: String) {
emailView.text = text
}
override fun getPassword(): String = passwordView.text.toString()
override fun setPassword(text: String) {
passwordView.text = text
}
override fun setEmailError(id: Int?) {
emailView.setErrorId(id)
}
override fun setPasswordError(id: Int?) {
passwordView.setErrorId(id)
}
override fun requestEmailFocus() {
emailView.requestFocus()
}
override fun requestPasswordFocus() {
passwordView.requestFocus()
}
override fun showProgress(show: Boolean) {
progressView.visibility = if (show) View.VISIBLE else View.GONE
loginFormView.visibility = if (show) View.GONE else View.VISIBLE
}
override var progressVisible by bindToLoading(R.id.progressView, R.id.loginFormView)
override var email by bindToText(R.id.emailView)
override var password by bindToText(R.id.passwordView)
override var emailErrorId by bindToErrorId(R.id.emailView)
override var passwordErrorId by bindToErrorId(R.id.passwordView)
override val emailRequestFocus by bindToRequestFocus(R.id.emailView)
override val passwordRequestFocus by bindToRequestFocus(R.id.passwordView)
https://github.com/MarcinMoskala/KotlinAndroidViewBindings
interface LoginView {
var progressVisible: Boolean
var email: String
var password: String
var emailErrorId: Int?
var passwordErrorId: Int?
val emailRequestFocus: () -> Unit
val passwordRequestFocus: () -> Unit
}
interface LoginView {
fun showProgress(show: Boolean)
fun getEmail(): String
fun setEmail(text: String)
fun getPassword(): String
fun setPassword(text: String)
fun setEmailError(id: Int?)
fun setPasswordError(id: Int?)
fun requestEmailFocus()
fun requestPasswordFocus()
}
https://github.com/MarcinMoskala/KotlinAndroidViewBindings
fun Activity.bindToEditText(@IdRes editTextId: Int): ReadWriteProperty<Any?, String>
= bindToEditText { findViewById(editTextId) as EditText }
private fun bindToEditText(viewProvider: () -> EditText): ReadWriteProperty<Any?, String>
= EditTextViewTextBinding(lazy(viewProvider))
private class EditTextViewTextBinding(
lazyViewProvider: Lazy<EditText>
) : ReadWriteProperty<Any?, String> {
val view by lazyViewProvider
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return view.text.toString()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
view.setText(value)
}
}
val CAN_EAT_PIE_KEY = "canEatPieKey"
val editor = preferences.edit();
editor.putBoolean(CAN_EAT_PIE_KEY, true);
editor.commit();
if(preferences.getBoolean(CAN_EAT_PIE_KEY, false)) {
// Code
}
val Context.preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(this)
preferences.canEatPie = true
if(preferences.canEatPie) {
// Code
}
https://github.com/MarcinMoskala/KotlinPreference
var SharedPreferences.canEatPie: Boolean by bindToPreferenceField(true, "CanEatPieKey")
var SharedPreferences.experience: Float? by bindToPreferenceFieldNullable()
var SharedPreferences.allPieInTheWorld: Long by bindToPreferenceField(0, "AllPieKey")
var SharedPreferences.savedGame: Game? by bindToPreferenceFieldNullable()
// Usage
stopDietButton.setOnClickListener {
preferences.canEatPie = true
}
startDietButton.setOnClickListener {
preferences.canEatPie = false
}
eatPieButton.setOnClickListener {
if(preferences.canEatPie) {
toast("Heah, pie :D")
} else {
toast("I cannot? :(")
}
}
https://github.com/MarcinMoskala/PreferenceHolder
object Pref: PreferenceHolder() {
var canEatPie: Boolean by bindToPreferenceField(true, "CanEatPieKey")
var longList: Map<Int, Long> by bindToPreferenceField(mapOf(0 to 12L, 10 to 143L))
var savedGame: Game? by bindToPreferenceFieldNullable("GameKey")
}
// Usage
stopDietButton.setOnClickListener {
Pref.canEatPie = true
}
startDietButton.setOnClickListener {
Pref.canEatPie = false
}
eatPieButton.setOnClickListener {
if(Pref.canEatPie) {
toast("Heah, pie :D")
} else {
toast("I cannot? :(")
}
}
override var email by bindToText(R.id.emailView)
private var _userRepo: UserRepository? = null
private val userRepoLock = Any()
val userRepo: UserRepository
get() {
synchronized(userRepoLock) {
if (_userRepo == null) {
_userRepo = UserRepository()
}
return _userRepo!!
}
}
val userRepo by lazy { UserRepository() }
val emailView by lazy { findViewById(R.is.email_view) }
override fun getEmail(): String
= emailView.text.toString()
override fun setEmail(text: String) {
emailView.text = text
}
vs
vs
override var email: String by bindToText(R.id.emailView)
email = "marcinmoskala@gmail.com"
if(email.matches(EMAIL_REGEX)) { /* ... */ }
interface MainView {
var email: String
}
val userRepo: LazyProperty<UserRepository>
= LazyProperty { UserRepository() }
userRepo.getValue().getUser()
val userRepo: UserRepository by lazy { UserRepository() }
userRepo.getUser()
override var email: BindToText = BindToText(R.id.emailView)
email.setValue("marcinmoskala@gmail.com")
if(email.getValue().matches(EMAIL_REGEX)) { /* ... */ }
interface MainView {
var email: BindToText
}
vs
vs
Marcin Moskała
marcinmoskala.com
github.com/MarcinMoskala
@marcinmoskala
Senior Android Developer in Gamekit, writing articles, open-source, freelancing and consulting.
marcinmoskala@gmail.com