Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

HOMO KOTLIN A brief History of Kotlin Development in Agoda

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

COGNITIVE REVOLUTION 70,000 BC

Slide 6

Slide 6 text

.subscribe(result -> { saveId(); }, error -> { displayError(); });

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

*.class Java complier

Slide 9

Slide 9 text

*.dex *.class Java complier application.apk

Slide 10

Slide 10 text

*.dex *.class Java complier Kotlin complier application.apk

Slide 11

Slide 11 text

*.dex *.class Java complier Kotlin complier application.apk :app:compileDebugKotlin :app:compileDebugJavaWithJavac

Slide 12

Slide 12 text

fun String.isThaiCharacter(): Boolean

Slide 13

Slide 13 text

fun String.isThaiCharacter(): Boolean public final class Extension { public static final boolean isThaiCharacter(@NotNull String $var) }

Slide 14

Slide 14 text

data class Player( val id: Int, val name: String, var level: Int)

Slide 15

Slide 15 text

data class Player( val id: Int, val name: String, var level: Int)

Slide 16

Slide 16 text

data class Player( val id: Int, val name: String, var level: Int)

Slide 17

Slide 17 text

data class Player( val id: Int, val name: String, var level: Int)

Slide 18

Slide 18 text

data class Player( val id: Int, val name: String, var level: Int)

Slide 19

Slide 19 text

data class Player( val id: Int, val name: String, var level: Int) public String toString() { return "Player(id=" + this.id + ", name=" + this.name + ", level=" + this.level + ")"; }

Slide 20

Slide 20 text

public static final int USER = 1;

Slide 21

Slide 21 text

public static final int USER = 1; interface Player { fun name(other: T): String fun upgradePlayer(): R }

Slide 22

Slide 22 text

public static final int USER = 1; sealed class Error { object UserNotFound : Error() object ExpiredSession : Error() } interface Player { fun name(other: T): String fun upgradePlayer(): R }

Slide 23

Slide 23 text

public static final int USER = 1; sealed class Error { object UserNotFound : Error() object ExpiredSession : Error() } interface Player { fun name(other: T): String fun upgradePlayer(): R } private inline fun perform

Slide 24

Slide 24 text

AGRICULTURAL REVOLUTION 4000 BC 70,000 BC

Slide 25

Slide 25 text

Why Testing?

Slide 26

Slide 26 text

> 10k Test cases > 1000 ATs Why Testing?

Slide 27

Slide 27 text

• More readable test code • Explore Kotlin features Goals:

Slide 28

Slide 28 text

• More readable test code • Explore Kotlin features Goals:

Slide 29

Slide 29 text

View Actions View Matchers View Assertions

Slide 30

Slide 30 text

open class VActions { fun click() = ViewActions.click() … } open class VAssertions { fun isNotEnabled() = ViewAssertions.matches(not(isEnabled())) fun doesNotExist() = ViewAssertions.doesNotExist() … }

Slide 31

Slide 31 text

fun expectToBeVisible() { interaction(vm.viewPageContent, va.isDisplayed()) }

Slide 32

Slide 32 text

• Lots of interesting features • Still not reach our goals!! What we found so far:

Slide 33

Slide 33 text

• Lots of interesting features • Still not reach our goals!! What we found so far: LET’S EXPLORE MORE!!!

Slide 34

Slide 34 text

High-order functions

Slide 35

Slide 35 text

High-order functions “a function that takes functions as parameters, or returns a function”

Slide 36

Slide 36 text

class HTML() { fun body(Body.() -> Unit) { ... } }

Slide 37

Slide 37 text

class HTML() { fun body(Body.() -> Unit) { ... } } fun html(HTML.() -> Unit) { ... }

Slide 38

Slide 38 text

class HTML() { fun body(Body.() -> Unit) { ... } } fun html(HTML.() -> Unit) { ... } html { body { ... } }

Slide 39

Slide 39 text

So, what can we do about it?

Slide 40

Slide 40 text

fun expectToBeVisible() { interaction(vm.layout, va.isDisplayed()) }

Slide 41

Slide 41 text

fun expectToBeVisible() { screen { layout { isDisplayed() } } } fun expectToBeVisible() { interaction(vm.layout, va.isDisplayed()) }

Slide 42

Slide 42 text

• Start to create essential matchers/actions/assertions • Convert some of our PageObject classes How we start?

Slide 43

Slide 43 text

class OccupancyPageKt { val screen = OccupancyScreen() fun shouldBeVisible() { screen { layout { isDisplayed() } } } fun shouldSeeGuestsAndRoomLabel() { screen { guestAndRoomLabel { isDisplayed() } } }

Slide 44

Slide 44 text

Finally, we share effort to the world!!

Slide 45

Slide 45 text

Our main contributor Ilya Lim Twitter: @S_Alviere

Slide 46

Slide 46 text

A bit of summary

Slide 47

Slide 47 text

A bit of summary • Experiment on non-production codebases

Slide 48

Slide 48 text

A bit of summary • Experiment on non-production codebases • Explore on the field we know best

Slide 49

Slide 49 text

A bit of summary • Experiment on non-production codebases • Explore on the field we know best • Contribute back to community

Slide 50

Slide 50 text

1500 70,000 BC UNIFICATION ANDROID DEVELOPER 4000 BC

Slide 51

Slide 51 text

What should be a first step?

Slide 52

Slide 52 text

What should be a first step?

Slide 53

Slide 53 text

Why we start with companion app?

Slide 54

Slide 54 text

Why we start with companion app? • Isolate from production code • Make us familiar android environment • Easier to evaluate new idea Image from github.com/artem-zinnatullin/qualitymatters

Slide 55

Slide 55 text

What we found in build process?

Slide 56

Slide 56 text

What we found in build process? • Build time

Slide 57

Slide 57 text

What we found in build process? • Build time -> Not Significant different

Slide 58

Slide 58 text

What we found in build process? • Build time -> Not Significant different • APK Size

Slide 59

Slide 59 text

What we found in build process? • Build time -> Not Significant different • APK Size -> ~400KB Added

Slide 60

Slide 60 text

What we found in build process? • Build time -> Not Significant different • APK Size -> ~400KB Added • Methods Count

Slide 61

Slide 61 text

What we found in build process? • Build time -> Not Significant different • APK Size -> ~400KB Added • Methods Count -> ~2k methods

Slide 62

Slide 62 text

What about Tools?

Slide 63

Slide 63 text

What about Tools? • Jacoco -> Coverage Report • Detekt -> Static Code Analysis

Slide 64

Slide 64 text

Let’s start in production app

Slide 65

Slide 65 text

Let’s start in production app

Slide 66

Slide 66 text

We need a plan!! • Selected team starting to write a new feature with Kotlin.

Slide 67

Slide 67 text

We need a plan!! • Knowledge Sharing for all the teams

Slide 68

Slide 68 text

We need a plan!! • Define code style!!

Slide 69

Slide 69 text

We need a plan!! • Channel to ask questions or suggestions

Slide 70

Slide 70 text

After half a year…

Slide 71

Slide 71 text

After half a year… • Everyone is writing Kotlin now.

Slide 72

Slide 72 text

After half a year… • Everyone is writing Kotlin now. • We still sharing knowledge & Best practices among developers

Slide 73

Slide 73 text

After half a year… • Everyone is writing Kotlin now. • We still sharing knowledge & Best practices among developers • Problems (e.g. code generation coverage)

Slide 74

Slide 74 text

Code Generation Problems data class InstayFeedbackCategoryEntity( val id: Int, val title: String )

Slide 75

Slide 75 text

Code Generation Problems public final class InstayFeedbackCategoryEntity { … public int hashCode() { return this.id * 31 + (this.title != null?this.title.hashCode():0); } public boolean equals(Object var1) { if(this != var1) { if(var1 instanceof InstayFeedbackCategoryEntity) { InstayFeedbackCategoryEntity var2 = (InstayFeedbackCategoryEntity)var1; if(this.id == var2.id && Intrinsics.areEqual(this.title, var2.title)) { return true; } } return false; } else { return true; } } }

Slide 76

Slide 76 text

Solutions https://medium.com/@andrey.fomenkov/kotlin-jacoco-tuning-compiler-to-skip- generated-code-935fcaeaa391

Slide 77

Slide 77 text

1900s 70,000 BC 4000 BC SCIENTIFIC REVOLUTION PART 4 1500s

Slide 78

Slide 78 text

How we use Kotlin to make Android Development better?

Slide 79

Slide 79 text

Data class

Slide 80

Slide 80 text

@AutoValue public abstract class HotelDetailEntity { public abstract int hotelId(); public abstract String basicInformation(); public abstract List images(); public abstract List attractions(); public abstract List facilityIds(); public abstract List usefulInformation(); public abstract List reviews(); public static Builder builder() { return new AutoValue_HotelDetailEntity.Builder(); } @AutoValue.Builder public abstract static class Builder { public abstract HotelDetailEntity.Builder hotelId(int hotelId); public abstract HotelDetailEntity.Builder basicInformation(String basicInformation); public abstract HotelDetailEntity.Builder images(List images); public abstract HotelDetailEntity.Builder attractions(List attractionEntities); public abstract HotelDetailEntity.Builder facilityIds(List facilityIds); public abstract HotelDetailEntity.Builder usefulInformation(List usefulInformation); public abstract HotelDetailEntity.Builder reviews(List reviews); public abstract HotelUsefulInformationEntity build(); } }

Slide 81

Slide 81 text

@AutoValue public abstract class HotelDetailEntity { public abstract int hotelId(); public abstract String basicInformation(); public abstract List images(); public abstract List attractions(); public abstract List facilityIds(); public abstract List usefulInformation(); public abstract List reviews(); public static Builder builder() { return new AutoValue_HotelDetailEntity.Builder(); } @AutoValue.Builder public abstract static class Builder { public abstract HotelDetailEntity.Builder hotelId(int hotelId); public abstract HotelDetailEntity.Builder basicInformation(String basicInformation); public abstract HotelDetailEntity.Builder images(List images); public abstract HotelDetailEntity.Builder attractions(List attractionEntities); public abstract HotelDetailEntity.Builder facilityIds(List facilityIds); public abstract HotelDetailEntity.Builder usefulInformation(List usefulInformation); public abstract HotelDetailEntity.Builder reviews(List reviews); public abstract HotelUsefulInformationEntity build(); } }

Slide 82

Slide 82 text

data class HotelDetailEntity( val hotelId: Int, val basicInformation: BasicInformationEntity, val images: ImagesEntity, val attractions: List val facilityIds: List, val usefulInformation: List, val reviews: List )

Slide 83

Slide 83 text

Custom View

Slide 84

Slide 84 text

public class CheckInCheckOutDayTimeView extends LinearLayout { private TextView checkInLabel; private TextView checkOutLabel; public CheckInCheckOutDayTimeView(final Context context) { super(context); initView(); } public CheckInCheckOutDayTimeView(final Context context, @Nullable final AttributeSet attrs) { super(context, attrs); initView(); } public CheckInCheckOutDayTimeView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CheckInCheckOutDayTimeView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(); } private void initView() { View.inflate(getContext(), R.layout.layout_check_in_check_out_view, this); checkInLabel = findViewById(R.id.check_in_label); checkOutLabel = findViewById(R.id.check_out_label); } }

Slide 85

Slide 85 text

public class CheckInCheckOutDayTimeView extends LinearLayout { private TextView checkInLabel; private TextView checkOutLabel; public CheckInCheckOutDayTimeView(final Context context) { super(context); initView(); } public CheckInCheckOutDayTimeView(final Context context, @Nullable final AttributeSet attrs) { super(context, attrs); initView(); } public CheckInCheckOutDayTimeView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CheckInCheckOutDayTimeView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(); } private void initView() { View.inflate(getContext(), R.layout.layout_check_in_check_out_view, this); checkInLabel = findViewById(R.id.check_in_label); checkOutLabel = findViewById(R.id.check_out_label); } }

Slide 86

Slide 86 text

class CheckInCheckOutDayTimeView : LinearLayout { private val checkInLabel by lazy { findViewById(R.id.check_in_label) } private val checkOutLabel by lazy { findViewById(R.id.check_out_label) } @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) @TargetApi(Build.VERSION_CODES.LOLLIPOP) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) init { View.inflate(context, R.layout.layout_check_in_check_out_view, this) } }

Slide 87

Slide 87 text

Collection Function

Slide 88

Slide 88 text

public void setItems(final List items) { gridLayout.removeAllViews(); for (final String item : items) { if (!item.isEmpty()) { final TextView itemView = createItemView(item); gridLayout.addView(itemView); } } }

Slide 89

Slide 89 text

public void setItems(final List items) { gridLayout.removeAllViews(); for (final String item : items) { if (!item.isEmpty()) { final TextView itemView = createItemView(item); gridLayout.addView(itemView); } } }

Slide 90

Slide 90 text

fun setItems(texts: List) { gridLayout.removeAllViews() texts.asSequence() .filter(String::isNotEmpty) .map(::createItemView) .forEach(gridLayout::addView) }

Slide 91

Slide 91 text

Safe Cast

Slide 92

Slide 92 text

public class SimpleFragment extends Fragment { interface Listener { void showErrorDialog(final Throwable throwable); } public void onError(final Throwable throwable) { final Activity activity = getActivity(); if (activity instanceof Listener) { ((Listener) activity).showErrorDialog(throwable); } } }

Slide 93

Slide 93 text

public class SimpleFragment extends Fragment { interface Listener { void showErrorDialog(final Throwable throwable); } public void onError(final Throwable throwable) { final Activity activity = getActivity(); if (activity instanceof Listener) { ((Listener) activity).showErrorDialog(throwable); } } }

Slide 94

Slide 94 text

class SimpleFragment : Fragment() { interface Listener { fun showErrorDialog(throwable: Throwable) } fun onError(throwable: Throwable) { (activity as? Listener)?.showErrorDialog(throwable) } }

Slide 95

Slide 95 text

Dagger

Slide 96

Slide 96 text

@Inject lateinit var sneakerApi: SneakerApi @field:[Inject Named("layoutId")] @JvmField var layoutId: Int = 0

Slide 97

Slide 97 text

@Module class DataModule { @Provides fun provideSneakerRepository(sneakerApi: SneakerApi) = SneakerRepositoryImpl(sneakerApi) }

Slide 98

Slide 98 text

@Module class DataModule { @Provides fun provideSneakerRepository(sneakerApi: SneakerApi) = SneakerRepositoryImpl(sneakerApi) } @Module class DataModule { @Provides fun provideSneakerRepository(sneakerApi: SneakerApi): SneakerRepository = SneakerRepositoryImpl(sneakerApi) }

Slide 99

Slide 99 text

Nullable from Java class

Slide 100

Slide 100 text

@AutoValue public abstract class HotelDetailEntity { public abstract int hotelId(); public abstract String basicInformation(); public abstract List images(); public abstract List reviews(); }

Slide 101

Slide 101 text

@AutoValue public abstract class HotelDetailEntity { public abstract int hotelId(); public abstract String basicInformation(); public abstract List images(); public abstract List reviews(); }

Slide 102

Slide 102 text

@AutoValue public abstract class HotelDetailEntity { public abstract int hotelId(); @NotNull public abstract String basicInformation(); @NotNull public abstract List images(); @Nullable public abstract List reviews(); }

Slide 103

Slide 103 text

Result • Development is easier, faster and more happy.

Slide 104

Slide 104 text

Result • Development is easier, faster and more happy. • Goodbye Java :P

Slide 105

Slide 105 text

Result • Development is easier, faster and more happy. • Goodbye Java :P • Kotlin forever !!

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

HALL OF FAME https://github.com/agoda-com/fork https://github.com/VerachadW/kraph IVAN BALAKSHA VERACHAD W. BIRTH https://github.com/Judrummer/JxAdapter TIPATAI P. JU https://github.com/andreyfomenkov/kotlin ANDREI FOMENKOV

Slide 108

Slide 108 text

ขอบคุณ THANK YOU! DESIGNED BY NETI SHEVAKIDAKARN