Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java Alternatives

GDG SPb
April 05, 2017

Java Alternatives

Видео:
Groovy: https://youtu.be/5KGBWjeaVfc
Kotlin: https://youtu.be/KrEH_51c-Co
Scala: https://youtu.be/NJ3dOkWaFBI

Разработчики делятся своим опытом использовании Groovy, Kotlin, Scala в отдельно взятом Android-приложении и цене интеграции альтернативных технологий в существующий проект.

1) Groovy
• Что такое Groovy?
• Сложность перехода с Java на Groovy
• Interoperability
• Чем Groovy runtime отличается от Java runtime?
• Как изменился процесс разработки?
• А как у Groovy совместимость с современными библиотеками?
• Стоит ли тянуть Groovy к себе в проект?

2) Kotlin
• Как подключить Kotlin в проект?
• Сравнение Kotlin и Java
• Какие преимущества дает использование Kotlin?
• Подводные камни при разработке на Kotlin?
• Выводы

3) Scala
• Какую систему сборки выбрать, Gradle или SBT?
• Полезные возможности Scala
• Поддержка Scala в IDE
• Проблемы Scala
• Выводы

Авторы:
1) Сергей Боиштян
Android-разработчик в Tinkoff Business (мобильный банк для юр. лиц). В Android-мир Сергей пришел после legacy-back проекта на Java, теперь кошмары про «кровавый enterprise» Сергею снятся реже.

2) Антон Александров
Android-разработчик внутренних мобильных приложений Тинькофф Банка. Впервые Антон столкнулся с Android летом 2012, и это время вспоминается с теплом. Тогда код писался как под Android, так и под IOS платформу. Что же можно сказать? Android победил!

3) Александр Максаков
Разработчик Android-приложения «За границу!». Начинал свою карьеру в outsource, желание участвовать в продуктовой разработке привели Александра в Тинькофф Банк, где за два года работы он успел поучаствовать в целом ряде проектов.

GDG SPb

April 05, 2017
Tweet

More Decks by GDG SPb

Other Decks in Technology

Transcript

  1. Зачем? (java “многословная”) -> (java любит NPE) -> (java решает

    существующие задачи эффективно) (java старая) -> (процесс разработки) -> (инженерный интерес) ->
  2. • Плюсы: ◦ Бесплатно и с исходниками ◦ Для получения

    версий для iOS и Windows достаточно реализовать нативный UI • Минусы: ◦ Очень медленная поддержка - от нескольких месяцев ◦ Подключение сторонних библиотек. Полу-ручное написание binding’ов Подходит для быстрой заказной разработки мобильных приложений на 3 платформы Другие альтернативы: C#, Xamarin
  3. • Плюсы: ◦ Общий фреймворк = ещё больше общего кода

    • Минусы: ◦ Не нативные компоненты UI. Трудно подключать сторонние ◦ Коммерческая лицензия стоит $ 3540.00 на разработчика в год ◦ “Свой” C++ Подходит для … тех, кто пользовался Qt ранее Другие альтернативы: C++, Qt
  4. • Плюсы: ◦ Бесплатно и с исходниками ◦ Переиспользование кода

    с web-версией ПО • Минусы: ◦ Не нативные компоненты UI. Трудно подключать стандартные и сторонние ◦ Ниже производительность и выше потребление памяти Подходит для быстрого прототипирования Другие альтернативы: JavaScript, ReactNative
  5. Syntax: Java @BindView(R.id.news_list_swipe_refresh) SwipeRefreshLayout swipeRefreshView; @BindView(R.id.news_list_error_view) View errorView; @BindView(R.id.news_list_recycler) RecyclerView

    recyclerView; @Inject ListMvp.Presenter presenter; @Inject NewsListAdapter adapter; private NewsListComponent component; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_news_list); ButterKnife.bind(this); getComponent().inject(this); recyclerView.setAdapter(adapter); presenter.onViewAttached(this); swipeRefreshView.setOnRefreshListener(() -> presenter.onRefresh()); }
  6. Syntax: Groovy @InjectView(R.id.news_list_swipe_refresh) SwipeRefreshLayout swipeRefreshView; @InjectView(R.id.news_list_error_view) View errorView; @InjectView(R.id.news_list_recycler) RecyclerView

    recyclerView; @Inject protected ListMvp.Presenter presenter; @Inject protected NewsListAdapter adapter; private NewsListComponent component; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); contentView = R.layout.activity_news_list; SwissKnife.inject(this); getComponent().inject(this); recyclerView.setAdapter(adapter); presenter.onViewAttached(this); swipeRefreshView.setOnRefreshListener { presenter.onRefresh() }; }
  7. Syntax view.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { doSomthing();

    } }); view.onClickListener = { doSomthing() } NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(appContext); notificationBuilder .setContentTitle("Title") .setContentText("Text") .setSmallIcon(R.drawable.ic_notification) .setWhen(10L); Notification notification = notificationBuilder.build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(appContext); notificationManager.notify(NOTIFY_ID, notification); appContext.showNotification(NOTIFY_ID) { contentTitle = "Title" contentText = "Text" smallIcon = R.drawable.ic_notification when = 10L }
  8. Syntax appContext.showNotification(NOTIFY_ID) { contentTitle = "Title" contentText = "Text" smallIcon

    = R.drawable.ic_notification when = 10L } static void showNotification(Context self, int id, @DelegatesTo(NotificationCompat.Builder) Closure closure) { NotificationManagerCompat.from(self).notify(id, notification(self, closure)) } static Notification notification(Context self, @DelegatesTo(NotificationCompat.Builder) Closure closure = null) { def builder = new NotificationCompat.Builder(self) if (closure) { builder.with(closure) } builder.build() }
  9. Groovy Sdk: Closure Closures { item++ } { -> item++

    } { println it } { it -> println it } def upperClosure = { name -> name.toUpperCase() } upperClosure(“hi”) upperClosure.call(“hi”) { String x, int y -> println "hey ${x} the value is ${y}" } { String x, int y = 2 -> println "hey ${x} the value is ${y}" }
  10. Groovy Sdk: Closure public Closure(Object owner, Object thisObject) { this.owner

    = owner; this.delegate = owner; this.thisObject = thisObject; final CachedClosureClass cachedClass = (CachedClosureClass) ReflectionCache.getCachedClass(getClass()); parameterTypes = cachedClass.getParameterTypes(); maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters(); }
  11. Groovy Sdk: Closure - this class Enclosing { def defineClosure(){

    def closure = { this } assert closure() == this } class Inner { def closure = { this } def defineClosure(){ def inner = new Inner() assert inner.closure() == inner } } }
  12. Groovy Sdk: Closure - owner class Enclosing { def defineClosure(){

    def closure = { owner } assert closure() == this } class Inner { def closure = { owner } def defineClosure(){ def inner = new Inner() assert inner.closure() == inner assert nestedClosure() == nestedClosure } def nestedClosure = { def cl = { owner } cl() } } }
  13. Groovy Sdk: Closure - delegate class Person { String name

    } class Thing { String name } def p = new Person(name: 'Scala') def t = new Thing(name: 'Kotlin') def upperCasedName = { delegate.name.toUpperCase() } upperCasedName.delegate = p assert upperCasedName() == 'SCALA' upperCasedName.delegate = t assert upperCasedName() == 'KOTLIN'
  14. Groovy Sdk Strings def javaString = 'java' def groovyString =

    "${javaString}" def j = '${javaString}' def bigGroovyString = """ ${javaString} ${groovyString} ${j} ${2 + 2} """ List, Map def list = [1, 2, 3] def map = [groovy: 'good'] println list println list.class println map println map.groovy println map['groovy'] println map.class [1, 2, 3] class java.util.ArrayList [groovy:good] good good class java.util.LinkedHashMap java java ${javaString} 4
  15. Groovy Sdk: Ranges Ranges def a = "0123456789" def b

    = 1..5 println a.class println b.class println a[1..4] println a[1..-1] println a[-1..0] println a[1..<9] println a[b] class java.lang.String class groovy.lang.IntRange 1234 123456789 9876543210 12345678 12345
  16. Groovy Sdk Each 'qwerty'.each { print it } ('a'..'z').each {

    print it } filter ('a'..'z').findAll { el -> el in ['e', 'y', 'u', 'i', 'o', 'a'] }.each { print it + ' ' } qwerty abcdefghijklmnopqrstuvwxyz a e i o u y
  17. Groovy Sdk map (0..10).collect { el -> el * 10

    }.each { print it + ' ' } reduce def sum = (0..10).inject(0) { prev, elem -> return prev + elem } 0 10 20 30 40 50 60 70 80 90 100 55
  18. Groovy Sdk Spread operator *. parent*.action() == parent.collect { child

    -> child?.action } def x = [2, 3] [2, 3] def y = [0, 1, *x, 4] [0, 1, 2, 3, 4] def a = [3 : 'c', 4 : 'd'] [3 : ‘c’, 4 : ‘d’] def b = [1 : 'a', 2: 'b', * : a, 5 : 'e'] [1 : ‘a’, 2 : ‘b’, 3 : ‘c’, 4 : ‘d’, 5 : ‘e’] Elvis operator ?: def a def b = a ?: 'a' println a null println b a Null safety ?. println a?.toString() null
  19. build.gradle tinkoffnews/build.gradle classpath 'org.codehaus.groovy:groovy-android-gradle-plugin:1.1.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' tinkoffnews/app/build.gradle dependencies { compile

    'org.codehaus.groovy:groovy:2.4.8:grooid' compile 'com.arasthel:swissknife:1.4.0' apt "com.google.dagger:dagger-compiler:${rootProject.daggerVersion}" }
  20. build.gradle: Groovy configuration androidGroovy { sourceSets { main.groovy.srcDirs += 'src/main/java'

    } options { configure(groovyOptions) { javaAnnotationProcessing = true configurationScript = file("$projectDir/config/groovy-compile-options.groovy") } sourceCompatibility = '1.7' targetCompatibility = '1.7' } } import groovy.transform.CompileStatic /** * Adds custom compilation configuration to the groovy compiler. */ withConfig(configuration) { /** All classes will be statically compiled even if they do not have the `@CompileStatic` annotation. */ ast(CompileStatic) }
  21. Proguard? -dontwarn org.codehaus.groovy.** -dontwarn groovy** -keep class org.codehaus.groovy.vmplugin.** -keep class

    org.codehaus.groovy.runtime.** -keepclassmembers class org.codehaus.groovy.runtime.dgm* {*;} -keepclassmembers class ** implements org.codehaus.groovy.runtime.GeneratedClosure {*;} -keepclassmembers class org.codehaus.groovy.reflection.GroovyClassValue* {*;}
  22. Proguard? Warning: ru.tinkoff.news.list.ListMvp: can't find referenced class ru.tinkoff.news.list.ListMvp$View$1 Warning: ru.tinkoff.news.list.ListMvp:

    can't find referenced class ru.tinkoff.news.list.ListMvp$Presenter$1 public interface ListMvp { interface View { void showData(List<NewsTitle> data); void showError(); void showProgress(); void hideProgress(); } interface Presenter { void onViewAttached(View view); void onRefresh(); void onViewDetached(); } }
  23. Dagger2? androidGroovy { skipJavaC = false //by default false options

    { configure(groovyOptions) { javaAnnotationProcessing = true } } }
  24. ButterKnife? SwissKnife compile 'com.arasthel:swissknife:1.4.0' @OnClick, @OnLongClick @OnItemClick, @OnItemLongClick @OnItemSelected, @OnChecked

    @OnFocusChanged, @OnTouch @OnPageChanged, @OnTextChanged @SaveInstance, @Parcelable @Extra, @Res, etc.
  25. 46 Gradle buildscript { ext { kotlinVersion = '1.0.6' }

    dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } //... apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' //... dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.kotlinVersion" kapt 'com.jakewharton:butterknife-compiler:8.4.0' kapt "com.google.dagger:dagger-compiler:${rootProject.daggerVersion}” }
  26. private String name = "name"; public void setName(String name) {

    this.name = name; } public String getName() { return name; } Java Kotlin 47 Свойства
  27. private String name = "name"; public void setName(String name) {

    this.name = name; } public String getName() { return name; } var name = "name" Java Kotlin 48 Свойства
  28. private String name = "name"; public void setName(String name) {

    this.name = name; } public String getName() { return name; } var name = "name" Java Kotlin 49 Свойства get() { return field } set(value) { field = value }
  29. fun getName(): String = "name" Java Kotlin public String getName()

    { return "name"; } 50 Возвращаем значения
  30. fun getName(): String = "name" Java Kotlin public String getName()

    { return "name"; } fun getName() = "name" 51 Возвращаем значения
  31. fun getName(): String = "name" Java Kotlin public void setName(String

    name) { this.name = name; } fun setName(name: String) { this.name = name } public String getName() { return "name"; } fun getName() = "name" 52 Возвращаем значения
  32. Java Kotlin public void init(String name) { init(name, "Текст"); }

    public void init(String name, String lastName) { //... } 53 Значения по умолчанию
  33. Java Kotlin public class Position { public Position(int x) {

    this(x, x); } public Position(int x, int y) { //... } } 54 Значения по умолчанию
  34. fun init(name: String, lastName: String = "Текст") { // ...

    } Java Kotlin public void init(String name) { init(name, "Текст"); } public void init(String name, String lastName) { //... } 55 Значения по умолчанию
  35. Java Kotlin public class Position { public Position(int x) {

    this(x, x); } public Position(int x, int y) { //... } } class Position(x: Int, y: Int = x) 56 Значения по умолчанию
  36. Java Kotlin class Position(x: Int, y: Int = x) public

    class Position { private int x; private int y; public Position(int x) { this(x, x); } public Position(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } } 57 Значения по умолчанию
  37. Java Kotlin class Position(x: Int, y: Int = x) class

    Position(var x: Int, var y: Int = x) public class Position { private int x; private int y; public Position(int x) { this(x, x); } public Position(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } } 58 Значения по умолчанию
  38. Java Kotlin public class Position { private int x; public

    Position(int x) { this.x = x; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Position position = (Position) o; return x == position.x; } @Override public int hashCode() { return x; } @Override public String toString() { return "Position{" + "x=" + x + '}'; } } 59 Data
  39. data class Position(private val x: Int) Java Kotlin public class

    Position { private int x; public Position(int x) { this.x = x; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Position position = (Position) o; return x == position.x; } @Override public int hashCode() { return x; } @Override public String toString() { return "Position{" + "x=" + x + '}'; } } 60 Data
  40. 62 Null Safety private var date: Date? = null private

    lateinit var date: Date private var date: Date
  41. 63 Null Safety private var date: Date? = null fun

    getTime(): Long? = date?.time private lateinit var date: Date private var date: Date
  42. 64 Null Safety private var date: Date? = null fun

    getTimeNotNull(): Long = date?.time ?: 42 fun getTime(): Long? = date?.time private lateinit var date: Date private var date: Date
  43. 65 Null Safety private var date: Date? = null fun

    getNPE(): Long = date!!.time fun getTimeNotNull(): Long = date?.time ?: 42 fun getTime(): Long? = date?.time private lateinit var date: Date private var date: Date
  44. when (any) { is Apple -> { any.bite() } "green",

    "android" -> { } 42 -> { } in 1..10 -> { } else -> { throw IllegalStateException() } } 66 When
  45. 67 val block: (String) -> String = { str ->

    str } val block2: (String) -> String = { it } Lambda
  46. 68 Extensions fun View.hide() { visibility = View.GONE } fun

    View.show() { visibility = View.VISIBLE }
  47. 69 Extensions fun View.hide() { visibility = View.GONE } fun

    View.show() { visibility = View.VISIBLE } private fun Toolbar.initToolbar() { setNavigationIcon(R.drawable.ic_arrow_back_white_24dp) setNavigationOnClickListener { presenter.onNavigationClick() } setTitle(R.string.title_time_range) }
  48. 70 val block: (String) -> String = { str ->

    str } val block2: (String) -> String = { it } Lambda private fun createDialog() = AlertDialog.Builder(context) .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() presenter.onPositiveClick() } .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() presenter.onNegativeClick() }.create()
  49. val list = listOf("one", "two", "three") val map = mapOf("one"

    to 1, "two" to 2, "three" to 3) val names = mutableListOf("App", "Two", "Three") 71 Колекции
  50. names .filter { it.startsWith("A") } .sortedBy { it } .map

    { it.toUpperCase() } .forEach(::print) val list = listOf("one", "two", "three") val map = mapOf("one" to 1, "two" to 2, "three" to 3) val names = mutableListOf("App", "Two", "Three") 72 Колекции
  51. names .filter { it.startsWith("A") } .sortedBy { it } .map

    { it.toUpperCase() } .forEach(::print) val list = listOf("one", "two", "three") val map = mapOf("one" to 1, "two" to 2, "three" to 3) val names = mutableListOf("App", "Two", "Three") names[0] = "value" val str = list[0] 73 Колекции
  52. 76 Standard.kt data.address?.let { view.setAddress(it) } if(data.address != null) {

    view.setAddress(data.address) } data.address?.run { view.setAddress(this) }
  53. 77 Standard.kt data.address?.let { view.setAddress(it) } if(data.address != null) {

    view.setAddress(data.address) } data.address?.run { view.setAddress(this) } fun Context.createProgressDialog(text: String) = ProgressDialog(this).apply { setMessage(text) isIndeterminate = true setCancelable(false) setCanceledOnTouchOutside(false) }
  54. Build system: Option 2 - SBT scalaVersion := "2.11.8" enablePlugins(AndroidApp)

    versionCode := Some(1) versionName := Some("1.0") organization := "ru.tinkoff.news" platformTarget := "android-25" buildToolsVersion := Some("24.0.3") minSdkVersion := "15" javacOptions in Compile ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil val daggerVersion = "2.7" val supportVersion = "25.0.1" libraryDependencies ++= "com.squareup.retrofit2" % "retrofit" % "2.1.0" :: "com.android.support" % "appcompat-v7" % supportVersion :: "com.android.support" % "recyclerview-v7" % supportVersion :: "com.google.dagger" % "dagger" % daggerVersion :: "com.google.dagger" % "dagger-compiler" % daggerVersion % "provided" :: Nil
  55. Typed Resources // Java with Butter Knife // Scala with

    Typed Resources @BindView(R.id.news_list_swipe_refresh) SwipeRefreshLayout swipeRefreshView; @BindView(R.id.news_list_error_view) View errorView; @BindView(R.id.news_list_recycler) RecyclerView recyclerView; class NewsListActivity extends AppCompatActivity with TypedFindView with ListMvp.View with NewsListAdapter.Callback { lazy val swipeRefreshLayout = findView(TR.news_list_swipe_refresh) lazy val errorView = findViewById(R.id.news_list_error_view) lazy val recyclerView = findView(TR.news_list_recycler)
  56. Pattern Matching abstract class Notification case class Email(sourceEmail: String, title:

    String, body: String) extends Notification case class SMS(sourceNumber: String, message: String) extends Notification case class VoiceRecording(contactName: String, link: String) extends Notification def showNotification (notification: Notification): String = { notification match { case Email (email, title, _) => "You got an email from " + email + " with title: " + title case SMS (number, message) => "You got an SMS from " + number + "! Message: " + message case VoiceRecording (name, link) => "you received a Voice Recording from " + name + "! Click the link to hear it: " + link } }
  57. Implicit Arguments trait SystemService { @inline def layoutInflater(implicit ctx: Context)

    = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater] } class ExampleActivity extends AppCompatActivity with SystemService { def inflateErrorView(parent: ViewGroup) = { layoutInflater.inflate(R.layout.layout_error, parent, false) } }
  58. Implicit Conversions // Definition object Utils { implicit class StringImprovements(s:

    String) { def increment = s.map { c => (c + 1).toChar } } } // Usage "Hello Scala!".increment
  59. For Expressions List<String> cities = Arrays.asList("Moscow", "Saint Petersburg", "New York");

    List<String> relatives = Arrays.asList("Granddad", "Grandmother", "Dad", "Mom"); List<String> tourists = Arrays.asList("Ivan", "Masha"); List<String> postcards = new ArrayList<>(); for (String city : cities) { for (String tourist : tourists) { for (String relative : relatives) { if (relative.startsWith("G")) { postcards.add("Dear " + relative + ", here's a postcard for you, from " + city + ". Your’s " + tourist); } } } }
  60. For Expressions val cities = List("Moscow", "Saint Petersburg", "New York")

    val relatives = List("Granddad", "Grandmother", "Dad", "Mom") val tourists = List("Ivan", "Masha") for ( city <- cities tourist <- tourists relative <- relatives if relative.startsWith("G") ) yield s"Dear $relative, here's a postcard for you, from $city. Your’s $tourist"
  61. For Expressions val a: Option[Int] = None val b: Option[Int]

    = Some(1) a.getOrElse(0) b.filter(_ > 3)
  62. For Expressions def getLighter: Lighter = Lighter() def getCharcoal: Charcoal

    = Charcoal() def getMeat: Meat = Meat() def getDinner: Dinner = { val lighter = getLighter val meat = getMeat val charcoal = getCharcoal val dinner = if (lighter != null & meat != null & charcoal != null) { val fire = Fire(charcoal, lighter) Dinner(meat, fire) } else { null } if (dinner != null) dinner else orderPizza() }
  63. For Expressions def getLighter: Option[Lighter] = Some(Lighter()) def getCharcoal: Option[Charcoal]

    = Some(Charcoal()) def getMeat: Option[Meat] = Some(Meat()) def getDinner: Dinner = { val dinner: Option[Dinner] = for { lighter <- getLighter charcoal <- getCharcoal fire <- Fire(charcoal, lighter) meat <- getMeat } yield Dinner(meat, fire) dinner.getOrElse(orderPizza()) }
  64. Обучаемость sealed abstract class \/[+A, +B] extends Product with Serializable

    { final class SwitchingDisjunction[X](r: => X) { def <<?:(left: => X): X = \/.this match { case -\/(_) => left case \/-(_) => r } } def :?>>[X](right: => X): SwitchingDisjunction[X] = new SwitchingDisjunction[X](right) def isLeft: Boolean = this match { case -\/(_) => true case \/-(_) => false } def isRight: Boolean = this match { case -\/(_) => false case \/-(_) => true } }
  65. Сравнение: Основные требования к проекту • Решение часто встречающихся в

    работе проблем. • Использование популярных библиотек: retrofit2 rxjava dagger butterknife
  66. Ссылки https://bitbucket.org/tyshkan007/tinkoffnews/ - исходники https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/ - Using Project Kotlin for

    Android https://github.com/scala-android/sbt-android/ http://scala-android.org http://blog.scalac.io/2016/05/19/reflections-on-starting-android-project-with-scala.html https://www.47deg.com/blog/scala-on-android-preparing-the-environment/ https://kotlinlang.org/docs/reference/kotlin-doc.html https://github.com/Arasthel/SwissKnife https://github.com/groovy/groovy-android-gradle-plugin