Slide 1

Slide 1 text

Kotlin Data Model 黃千碩 (Kros) oSolve, Ltd./打⼯工趣 Mobile App Developer

Slide 2

Slide 2 text

Outline • Android data model 演化史 • Kotlin 介紹 • Kotlin classes/data classes 介紹 • Kotlin companion object 介紹 • Unit testing • What’s next?

Slide 3

Slide 3 text

Data Model 演化史 • 當第⼀一次在 Android 上寫 data model 時

Slide 4

Slide 4 text

Data Model 演化史 • 定義 property

Slide 5

Slide 5 text

public class User {
 private String name;
 private String email; 
 private int age;
 
 private Date createdAt;
 }

Slide 6

Slide 6 text

Data Model 演化史 • 定義 property • ⼿手動實作 get, set

Slide 7

Slide 7 text

Data Model 演化史 • 定義 property • ⼿手動實作 get, set

Slide 8

Slide 8 text

Data Model 演化史 • 定義 property • ⼿手動實作 get, set • IDE 可以幫忙產⽣生 setter, getter

Slide 9

Slide 9 text

Data Model 演化史 • 定義 property • ⼿手動實作 get, set • IDE 可以幫忙產⽣生 setter, getter, equal, hash, toString

Slide 10

Slide 10 text

public class User implements Parcelable {
 private String name;
 
 private String email;
 
 private int age;
 
 private Date createdAt;
 
 @Override
 public int hashCode() {
 int result = name != null ? name.hashCode() : 0;
 result = 31 * result + (email != null ? email.hashCode() : 0);
 result = 31 * result + age;
 result = 31 * result + (createdAt != null ? createdAt.hashCode() : 0);
 return result;
 }
 
 @Override
 public boolean equals(Object o) {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;
 
 User user = (User) o;
 
 if (age != user.age) return false;
 if (name != null ? !name.equals(user.name) : user.name != null) return false;
 if (email != null ? !email.equals(user.email) : user.email != null) return false;
 return createdAt != null ? createdAt.equals(user.createdAt) : user.createdAt == null;
 
 }
 
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + '\'' +
 ", email='" + email + '\'' +
 ", age=" + age +
 ", createdAt=" + createdAt +
 '}';
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public String getEmail() {
 return email;
 }
 
 public void setEmail(String email) {
 this.email = email;
 }
 
 public int getAge() {
 return age;
 }
 
 public void setAge(int age) {
 this.age = age;
 }
 
 public Date getCreatedAt() {
 return createdAt;
 }
 
 public void setCreatedAt(Date createdAt) {
 this.createdAt = createdAt;
 }

Slide 11

Slide 11 text

Parcelable • 遇到 parcelable

Slide 12

Slide 12 text

Parcelable • 遇到 parcelable • ⼿手動實作

Slide 13

Slide 13 text

Parcelable • 遇到 parcelable • ⼿手動實作

Slide 14

Slide 14 text

Parcelable • 遇到 parcelable • ⼿手動實作 • ⽤用 android studio plugin 產⽣生 parcelable 實作

Slide 15

Slide 15 text

public class User implements Parcelable {
 private String name;
 
 private String email;
 
 private int age;
 
 private Date createdAt;
 
 @Override
 public int hashCode() {
 int result = name != null ? name.hashCode() : 0;
 result = 31 * result + (email != null ? email.hashCode() : 0);
 result = 31 * result + age;
 result = 31 * result + (createdAt != null ? createdAt.hashCode() : 0);
 return result;
 }
 
 @Override
 public boolean equals(Object o) {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;
 
 User user = (User) o;
 
 if (age != user.age) return false;
 if (name != null ? !name.equals(user.name) : user.name != null) return false;
 if (email != null ? !email.equals(user.email) : user.email != null) return false;
 return createdAt != null ? createdAt.equals(user.createdAt) : user.createdAt == null;
 
 }
 
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + '\'' +
 ", email='" + email + '\'' +
 ", age=" + age +
 ", createdAt=" + createdAt +
 '}';
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public String getEmail() {
 return email;
 }
 
 public void setEmail(String email) {
 this.email = email;
 }
 
 public int getAge() {
 return age;
 }
 
 public void setAge(int age) {
 this.age = age;
 }
 
 public Date getCreatedAt() {
 return createdAt;
 }
 
 public void setCreatedAt(Date createdAt) {
 this.createdAt = createdAt;
 }
 
 @Override
 public int describeContents() { return 0; }
 
 @Override
 public void writeToParcel(Parcel dest, int flags) {
 dest.writeString(this.name);
 dest.writeString(this.email);
 dest.writeInt(this.age);
 dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1);
 }
 
 public User() {}
 
 protected User(Parcel in) {
 this.name = in.readString();
 this.email = in.readString();
 this.age = in.readInt();
 long tmpCreatedAt = in.readLong();
 this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt);
 }
 
 public static final Creator CREATOR = new Creator() {
 @Override
 public User createFromParcel(Parcel source) {return new User(source);}
 
 @Override
 public User[] newArray(int size) {return new User[size];}
 };


Slide 16

Slide 16 text

public class User implements Parcelable {
 private String name;
 
 private String email;
 
 private int age;
 
 private Date createdAt;
 
 @Override
 public int hashCode() {
 int result = name != null ? name.hashCode() : 0;
 result = 31 * result + (email != null ? email.hashCode() : 0);
 result = 31 * result + age;
 result = 31 * result + (createdAt != null ? createdAt.hashCode() : 0);
 return result;
 }
 
 @Override
 public boolean equals(Object o) {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;
 
 User user = (User) o;
 
 if (age != user.age) return false;
 if (name != null ? !name.equals(user.name) : user.name != null) return false;
 if (email != null ? !email.equals(user.email) : user.email != null) return false;
 return createdAt != null ? createdAt.equals(user.createdAt) : user.createdAt == null;
 
 }
 
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + '\'' +
 ", email='" + email + '\'' +
 ", age=" + age +
 ", createdAt=" + createdAt +
 '}';
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public String getEmail() {
 return email;
 }
 
 public void setEmail(String email) {
 this.email = email;
 }
 
 public int getAge() {
 return age;
 }
 
 public void setAge(int age) {
 this.age = age;
 }
 
 public Date getCreatedAt() {
 return createdAt;
 }
 
 public void setCreatedAt(Date createdAt) {
 this.createdAt = createdAt;
 }
 
 @Override
 public int describeContents() { return 0; }
 
 @Override
 public void writeToParcel(Parcel dest, int flags) {
 dest.writeString(this.name);
 dest.writeString(this.email);
 dest.writeInt(this.age);
 dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1);
 }
 
 public User() {}
 
 protected User(Parcel in) {
 this.name = in.readString();
 this.email = in.readString();
 this.age = in.readInt();
 long tmpCreatedAt = in.readLong();
 this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt);
 }
 
 public static final Creator CREATOR = new Creator() {
 @Override
 public User createFromParcel(Parcel source) {return new User(source);}
 
 @Override
 public User[] newArray(int size) {return new User[size];}
 };
 隨隨便便便便就超過 100 ⾏行行

Slide 17

Slide 17 text

Parcelable • 遇到 parcelable • ⼿手動實作 • ⽤用 plugin 產⽣生 Parcelable 實作 • 當 class 越來來越多,property 越來來越多,開發的⼈人越來來越多 • 每次實作就成了了問題

Slide 18

Slide 18 text

願望 • 如果可以這⼀一切可以⾃自動產⽣生就好!

Slide 19

Slide 19 text

Libraries • Google AutoValue
 https://github.com/google/auto • Lombok
 https://github.com/rzwitserloot/lombok


Slide 20

Slide 20 text

AutoValue • 只要定義好 interface,就會⾃自動產⽣生 model • ⾃自動產⽣生 setter, getter, equal, hash, toString • 有 plugin ⾃自動產⽣生 parcelable 實作 • Builder pattern

Slide 21

Slide 21 text

@AutoValue
 public abstract class User implements Parcelable {
 
 public abstract String name();
 
 @Nullable
 public abstract String email();
 
 public abstract int age();
 
 public abstract Date createdAt();
 
 public static User create(String name, String email, int age, Date createdAt) {
 return builder()
 .name(name)
 .email(email)
 .age(age)
 .createdAt(createdAt)
 .build();
 }
 
 public static Builder builder() {return new AutoValue_User.Builder();}
 
 @AutoValue.Builder
 public abstract static class Builder {
 
 public abstract Builder name(String name);
 
 public abstract Builder email(String email);
 
 public abstract Builder age(int age);
 
 public abstract Builder createdAt(Date createdAt);
 
 public abstract User build();
 }
 }

Slide 22

Slide 22 text

AutoValue • 除此之外,加入兩兩個概念念: • Immutable Object: 預設產⽣生的 object 都是 value object,沒有 setter,只有 getter • Null-safety: 每個 property 都需定義 NonNull, Nullable

Slide 23

Slide 23 text

@AutoValue
 public abstract class User implements Parcelable {
 
 public abstract String name();
 
 @Nullable
 public abstract String email();
 
 public abstract int age();
 
 public abstract Date createdAt();
 
 public static User create(String name, String email, int age, Date createdAt) {
 return builder()
 .name(name)
 .email(email)
 .age(age)
 .createdAt(createdAt)
 .build();
 }
 
 public static Builder builder() {return new AutoValue_User.Builder();}
 
 @AutoValue.Builder
 public abstract static class Builder {
 
 public abstract Builder name(String name);
 
 public abstract Builder email(String email);
 
 public abstract Builder age(int age);
 
 public abstract Builder createdAt(Date createdAt);
 
 public abstract User build();
 }
 }

Slide 24

Slide 24 text

趨勢 • 經由上⾯面的例例⼦子,我們可以發現 Data Model 的趨勢為以下三點: • ⾃自動產⽣生 (Code Generation) • Immutable Object • Null-safety

Slide 25

Slide 25 text

Kotlin 介紹 val name: String = "Android"

Slide 26

Slide 26 text

Kotlin 介紹 val name: String = "Android" val email: String? = null

Slide 27

Slide 27 text

Kotlin 介紹 val name: String = "Android" val email: String? = null var name: String = "Android"

Slide 28

Slide 28 text

Kotlin 介紹 val name: String = "Android" val email: String? = null var name: String = "Android"
 name = "Android Taipei"

Slide 29

Slide 29 text

Kotlin 介紹 val name: String = "Android" val email: String? = null var name: String = "Android"
 name = "Android Taipei" • val: Assign-once (read-only) local variable • var: Mutable variable:

Slide 30

Slide 30 text

Kotlin 介紹 fun doSomething(name: String) : String { return name.toUpperCase() }

Slide 31

Slide 31 text

Classes • 如何定義⼀一個 class

Slide 32

Slide 32 text

class User { }

Slide 33

Slide 33 text

class User constructor(
 name: String, email: String?,
 age: Int,
 createdAt: Date
 ) { } • 定義 constructor

Slide 34

Slide 34 text

class User(
 name: String, email: String?,
 age: Int,
 createdAt: Date
 ) { } • 可以省略略關鍵字

Slide 35

Slide 35 text

class User(
 name: String, email: String?,
 age: Int,
 createdAt: Date
 ) { var name: String = name, var email: String? = email,
 var age: Int = age,
 var createdAt: Date = createdAt } • 定義 properties

Slide 36

Slide 36 text

class User(
 var name: String, var email: String?,
 var age: Int,
 var createdAt: Date
 ) { } • 更更簡單的寫法

Slide 37

Slide 37 text

class User(
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) { } • 定義 immutable properties

Slide 38

Slide 38 text

class User(
 val name: String = "No Name", val email: String? = null,
 val age: Int = 0,
 val createdAt: Date = Date()
 ) { } • 給預設值

Slide 39

Slide 39 text

Data Classes • The compiler automatically derives the following members from all properties declared in the primary constructor: • 根據你定義的 properties,⾃自動幫你產⽣生以下四種內容 • equals()/hashCode() • toString() • componentN() functions • copy()

Slide 40

Slide 40 text

Data Classes • Primary constructor ⾄至少要有⼀一個參參數 • 參參數只能是 val, var

Slide 41

Slide 41 text

class User (
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) { }

Slide 42

Slide 42 text

data class User(
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) { }

Slide 43

Slide 43 text

Parcelable • 當然也有開發者提供 parcelable 的 plugin,⾃自動產⽣生 parcelable 實作

Slide 44

Slide 44 text

data class User(
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) { }

Slide 45

Slide 45 text

data class User(
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) : AutoParcelable { }

Slide 46

Slide 46 text

演化比較

Slide 47

Slide 47 text

data class User(
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) : AutoParcelable { } public class User implements Parcelable {
 private String name;
 
 private String email;
 
 private int age;
 
 private Date createdAt;
 
 @Override
 public int hashCode() {
 int result = name != null ? name.hashCode() : 0;
 result = 31 * result + (email != null ? email.hashCode() : 0);
 result = 31 * result + age;
 result = 31 * result + (createdAt != null ? createdAt.hashCode() : 0);
 return result;
 }
 
 @Override
 public boolean equals(Object o) {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;
 
 User user = (User) o;
 
 if (age != user.age) return false;
 if (name != null ? !name.equals(user.name) : user.name != null) return false;
 if (email != null ? !email.equals(user.email) : user.email != null) return false;
 return createdAt != null ? createdAt.equals(user.createdAt) : user.createdAt == null;
 
 }
 
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + '\'' +
 ", email='" + email + '\'' +
 ", age=" + age +
 ", createdAt=" + createdAt +
 '}';
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public String getEmail() {
 return email;
 }
 
 public void setEmail(String email) {
 this.email = email;
 }
 
 public int getAge() {
 return age;
 }
 
 public void setAge(int age) {
 this.age = age;
 }
 
 public Date getCreatedAt() {
 return createdAt;
 }
 
 public void setCreatedAt(Date createdAt) {
 this.createdAt = createdAt;
 }
 
 @Override
 public int describeContents() { return 0; }
 
 @Override
 public void writeToParcel(Parcel dest, int flags) {
 dest.writeString(this.name);
 dest.writeString(this.email);
 dest.writeInt(this.age);
 dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1);
 }
 
 public User() {}
 
 protected User(Parcel in) {
 this.name = in.readString();
 this.email = in.readString();
 this.age = in.readInt();
 long tmpCreatedAt = in.readLong();
 this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt);
 }
 
 public static final Creator CREATOR = new Creator() {
 @Override
 public User createFromParcel(Parcel source) {return new User(source);}
 
 @Override
 public User[] newArray(int size) {return new User[size];}
 };
 @AutoValue
 public abstract class User implements Parcelable {
 
 public abstract String name();
 
 @Nullable
 public abstract String email();
 
 public abstract int age();
 
 public abstract Date createdAt();
 
 public static User create(String name, String email, int age, Date createdAt) {
 return builder()
 .name(name)
 .email(email)
 .age(age)
 .createdAt(createdAt)
 .build();
 }
 
 public static Builder builder() {return new AutoValue_User.Builder();}
 
 @AutoValue.Builder
 public abstract static class Builder {
 
 public abstract Builder name(String name);
 
 public abstract Builder email(String email);
 
 public abstract Builder age(int age);
 
 public abstract Builder createdAt(Date createdAt);
 
 public abstract User build();
 }
 }

Slide 48

Slide 48 text

Copy • 很多時候我們會更更新 object 內容 • 如何更更新⼀一個 immutable object? • 利利⽤用 copy()

Slide 49

Slide 49 text

class User(
 val name: String, val email: String?
 ) { }

Slide 50

Slide 50 text

class User(
 val name: String, val email: String?
 ) { fun copy(name: String = this.name, email: String? = this.email): User {
 return User(name = name, email = email)
 } } • copy() 的實作如下

Slide 51

Slide 51 text

data class User(
 val name: String, val email: String?,
 val age: Int,
 val createdAt: Date
 ) : AutoParcelable { }

Slide 52

Slide 52 text

data class User(
 val name: String,
 val email: String?,
 val age: Int,
 val createdAt: Date
 ) : AutoParcelable {
 fun updateAge(newAge: Int): User {
 return copy(age = newAge)
 }
 }

Slide 53

Slide 53 text

data class User(
 val name: String,
 val email: String?,
 val age: Int,
 val createdAt: Date
 ) : AutoParcelable {
 fun updateAge(newAge: Int): User {
 return copy(age = newAge)
 }
 }

Slide 54

Slide 54 text

data class User(
 val name: String,
 val email: String?,
 val age: Int,
 val createdAt: Date
 ) : AutoParcelable {
 fun updateAge(newAge: Int): User {
 return copy(age = newAge)
 }
 } user = user.updateAge(18);
 assertThat(user.getAge()).isEqualTo(18);

Slide 55

Slide 55 text

Companion Object • Kotlin 中沒有 static 的概念念,改⽤用 Companion Object

Slide 56

Slide 56 text

Companion Object • Kotlin 中沒有 static 的概念念,改⽤用 Companion Object • Static method -> Companion method

Slide 57

Slide 57 text

class User(
 val name: String, val email: String?
 ) { }

Slide 58

Slide 58 text

class User(
 val name: String, val email: String?
 ) { companion object {
 fun myCompanionMethod() {
 // ...
 }
 } }

Slide 59

Slide 59 text

class User(
 val name: String, val email: String?
 ) { companion object {
 fun myCompanionMethod() {
 // ...
 }
 } } // Java 中使⽤用 User.Companion.myCompanionMethod();

Slide 60

Slide 60 text

Companion Object • Kotlin 中沒有 static 的概念念,改⽤用 Companion Object • Static method -> companion method • 舉例例:ViewModel (MVVM 中的 ViewModel)

Slide 61

Slide 61 text

User user = ... // age = 50 UserViewModel viewModel = createViewModel(user) viewModel.getAgeString(); // "50 歲"

Slide 62

Slide 62 text

class UserViewModel(
 context: Context,
 val user: User
 ) { }

Slide 63

Slide 63 text

class UserViewModel(
 context: Context,
 val user: User
 ) {
 val ageString: String
 }

Slide 64

Slide 64 text

class UserViewModel(
 context: Context,
 val user: User
 ) {
 val ageString: String
 
 init {
 ageString = context.getString(R.string.age_is, user.age.toString())
 }
 }

Slide 65

Slide 65 text

class UserViewModel(
 context: Context,
 val user: User
 ) {
 val ageString: String
 
 init {
 ageString = context.getString(R.string.age_is, user.age.toString())
 }
 } UserViewModel viewModel = new UserViewModel(context, user); viewModel.getAgeString(); // “50 歲"

Slide 66

Slide 66 text

Companion Object • 想要使⽤用 data classes • 使⽤用 companion method

Slide 67

Slide 67 text

data class UserViewModel(
 val user: User,
 val ageString: String
 ) { 
 }

Slide 68

Slide 68 text

data class UserViewModel(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 }

Slide 69

Slide 69 text

data class UserViewModel(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 }

Slide 70

Slide 70 text

Private Constructor • 與 Java 相同,可以 private constructor,讓維護更更容易易

Slide 71

Slide 71 text

data class UserViewModel(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 }

Slide 72

Slide 72 text

data class UserViewModel private constructor(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 }

Slide 73

Slide 73 text

data class UserViewModel private constructor(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 } // Java 中使⽤用 new UserViewModel();

Slide 74

Slide 74 text

data class UserViewModel private constructor(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 } // Java 中使⽤用 new UserViewModel();

Slide 75

Slide 75 text

data class UserViewModel private constructor(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 } // Java 中使⽤用 new UserViewModel(); UserViewModel viewModel = UserViewModel.Companion.create(context, user);

Slide 76

Slide 76 text

data class UserViewModel private constructor(
 val user: User,
 val ageString: String
 ) {
 companion object {
 fun create(context: Context, user: User): UserViewModel {
 val ageString = context.getString(R.string.age_is, user.age.toString())
 return UserViewModel(user, ageString)
 }
 }
 } // Java 中使⽤用 new UserViewModel(); UserViewModel viewModel = UserViewModel.Companion.create(context, user); viewModel.getAgeString(); // "50 歲"

Slide 77

Slide 77 text

Unit Test • Kotlin 所有的東⻄西,預設都是 final

Slide 78

Slide 78 text

Unit Test • Kotlin 所有的東⻄西,預設都是 final • 當要 Mock (2.0) 時,會出現以下問題: org.mockito.exceptions.base.MockitoException: 
 Cannot mock/spy class com.example.kotlindataclass.model.xxx cannot mock/spy because :
 - final class

Slide 79

Slide 79 text

Solution • 啟動 Mockito 2 的 option 功能 
 (opt-in mocking of final classes/methods) • 在路路徑 test/resources/mockito-extensions 底下新增檔案:
 org.mockito.plugins.MockMaker • 檔案內容:
 mock-maker-inline

Slide 80

Slide 80 text

Solution

Slide 81

Slide 81 text

如何開始導入 Kotlin 1.挑選最少⽤用到的 Data Model 2.對此 Data Model 寫測試 (encode/decode) 3.透過 Android Studio Converter (command+shift+a) 4.在⼀一個 commit 裡⼀一次做完 (要反悔悔也容易易)

Slide 82

Slide 82 text

What’s Next • ViewModel 採⽤用 Kotlin • Presenter 採⽤用 Kotlin • Helper 採⽤用 Kotlin • View 採⽤用 Kotlin • …

Slide 83

Slide 83 text

What’s Next • 資料操作 • Collections: List, Set, Map • map, filter, find…etc. • more…

Slide 84

Slide 84 text

Using Kotlin for Android today!

Slide 85

Slide 85 text

Reference • Kotlin Data Classes
 https://kotlinlang.org/docs/reference/data-classes.html • Kotlin 的 data class 會對你的程式產⽣生什什麼樣的化學反應
 https://ingramchen.io/blog/2017/06/kotlin-data-class.html • Kotlin Koans
 https://kotlinlang.org/docs/tutorials/koans.html • Mock the unmockable
 https://github.com/mockito/mockito/wiki/What%27s-new-in- Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods