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

Groovy в мире Android

Groovy в мире Android

Сергей Боиштян Tinkoff

Инструменты в разработке приложений важны, они влияют на скорость создания и качество продуктов. В своем докладе я расскажу вам о таком инструменте, как Groovy и его эффективном использовании в Android-мире. Мой рассказ основан на предыдущем докладе про альтернативный способ разработки приложений с помощью Groovy.

MOSDROID

May 21, 2017
Tweet

More Decks by MOSDROID

Other Decks in Programming

Transcript

  1. Что произойдет? class A { void foo() {} } interface

    Foo { void foo() } A a = new A() Foo foo = a as Foo foo.foo() 1) Не скомпилируется 2) Скомпилируется упадет в момент as 3) Cкомпилируется упадет в момент вызова foo() 4) Скомпилируется и выполнится успешно
  2. 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()); }
  3. 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() }; }
  4. 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 }
  5. Extension methods declaration 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() }
  6. Groovy Sdk: Closure { 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}" }
  7. Groovy Sdk: Closure creation this owner delegate by default ==

    owner public Closure(Object owner, Object thisObject) { this.owner = owner; this.delegate = owner; this.thisObject = thisObject; //…. }
  8. 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 } } }
  9. Groovy Sdk: Closure - this public Object defineClosure() { class

    _defineClosure_closure1 extends Closure implements GeneratedClosure { public _defineClosure_closure1() { super(/*owner*/ Enclosing.this, /*this*/ Enclosing.this); } //impl... }
  10. Groovy Sdk: Closure - owner class Enclosing { def defineClosure(){

    def closure = { owner } assert closure() == this } class Inner { def closure = { owner } def nestedClosure = { def cl = { owner } cl() } //... } }
  11. Groovy Sdk: Closure - owner class Enclosing { //... class

    Inner { def closure = { owner } def nestedClosure = { def cl = { owner } cl() } def defineClosure() { def inner = new Inner() assert inner.closure() == inner assert nestedClosure() == nestedClosure } } }
  12. Groovy Sdk: Closure - owner public Inner() { //... Enclosing.Inner._closure2

    var4 = new Enclosing.Inner._closure2(this); this.nestedClosure = var4; //... } public class _closure2 extends Closure implements GeneratedClosure { //... public Object doCall(Object it) { class _closure3 extends Closure implements GeneratedClosure { public _closure3(Object _thisObject) { super(/*owner*/ _closure2.this, /*this*/ _thisObject);} //... } _closure3 cl = new _closure3(this.getThisObject()); return cl.call(); }
  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. Category? use(TestCategory) { 1.test() } /*public*/ class TestCategory { public

    static test(Integer i) { println(i + " in test category") } } @Category(Integer) class TestCategory { /*public*/ void test() { println(this + " in test category") } }
  15. Category with static compilation? Error:(9, 9) Groovyc: [Static type checking]

    Due to their dynamic nature, usage of categories is not possible with static type checking active Error:(10, 13) Groovyc: [Static type checking] Cannot find matching method int#test(). Please check if the declared type is right and if the method exists.
  16. Groovy Sdk - Strings def javaString = 'java' def groovyString

    = "${javaString}" def j = '${javaString}' def bigGroovyString = """ ${javaString} ${groovyString} ${j} ${2 + 2} """ java java ${javaString} 4
  17. Groovy Sdk - 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 with static compile
  18. 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 public static String getAt(String text, Range range) { RangeInfo info = subListBorders(text.length(), range); String answer = text.substring(info.from, info.to); if(info.reverse) { answer = reverse((CharSequence)answer); } return answer; }
  19. 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
  20. 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
  21. Groovy Sdk - Null check Elvis operator ?: def a

    def b = a ?: 'a' println a null println b a Null safety ?. println a?.toString() null
  22. 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] ScriptBytecodeAdapter.despreadList( new Object[]{Integer.valueOf(0), Integer.valueOf(1), Integer.valueOf(4)}, new Object[]{x}, new int[]{2})) 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’]
  23. Groovy android plugin 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' } }
  24. Groovy android plugin 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) }
  25. 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* {*;}
  26. Proguard? Warning: ru.tinkoff.news.list.ListMvp: can't find referenced class ListMvp$View$1 Warning: ru.tinkoff.news.list.ListMvp:

    can't find referenced class 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(); } }
  27. Dagger2? androidGroovy { skipJavaC = false //by default false options

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

    @OnFocusChanged, @OnTouch @OnPageChanged, @OnTextChanged @SaveInstance, @Parcelable @Extra, @Res, etc.
  29. Groovy для gradle public class JavaStringGeneratorPlugin { public void apply(@NotNull

    Project project) { if(project.findProperty("stringGenerator") == null) { project.getExtensions().create("stringGenerator", StringGeneratorPluginProperties.class); } project.getTasks().create("updateStrings", UpdateStringsTask.class); } } class GroovyStringGeneratorPlugin { def apply(Project project) { if(!project.findProperty("stringGenerator")) { project.extensions.create("stringGenerator", StringGeneratorPluginProperties) } project.tasks.create("updateStrings", UpdateStringsTask) } }
  30. Используем Grgit public class JavaUseGrgitTask { @TaskAction void action() {

    Grgit grgit = Grgit.open(); grgit.add(); grgit.commit(); grgit.push(); } }
  31. Используем Grgit def methodMissing(String name, args) { OpSyntaxUtil.tryOp(this.class, OPERATIONS, [repository]

    as Object[], name, args) } static Object tryOp(Class service, Map supportedOps, Object[] classArgs, String methodName, Object[] methodArgs) { //... def op = supportedOps[methodName].newInstance(classArgs) ConfigureUtil.configure(op, config) return op.call() } private static final Map OPERATIONS = [add: AddOp, push: PushOp, commit: CommitOp /**/]
  32. Используем Grgit Возможные решения 1) Reflection 2) Не использовать Grgit,

    найти альтернативу 3) Написать таску на Groovy
  33. Используем Grgit Возможные решения 1) Reflection 2) Не использовать Grgit,

    найти альтернативу на другом языке 3) Написать таску на Groovy 4) Написать в файле *.gradle и применить его
  34. Используем Grgit task updateStringsWithPush(group: 'string generator', dependsOn: 'updateStrings') { description

    = "Commit generated string resource file" doLast { if (project.stringGenerator.isReplaced) { grgit.add(patterns: [addFilePath]) grgit.commit(message: "message", paths: [path]) grgit.push() } } }
  35. Выводы: Groovy для gradle Если не знаешь Groovy -> Если

    плагин без зависимостей Groovy -> Пишешь на чем удобно Если плагин небольшой -> Пишешь на чем удобно Если есть зависимость от Groovy -> Если хочешь костылить -> костылишь Eсли хочешь идти -> идешь Если не хочешь костылить -> учишь Groovy и идешь в начало Если плагин большой -> Eсли не хочешь учить Groovy -> пишешь долго и страдаешь Eсли хочешь учить Groovy -> учишь Groovy и страдаешь Ecли знаешь Groovy -> Сначала учишь всю команду Groovy и вы пишете и страдаете вместе
  36. Ссылки https://bitbucket.org/tyshkan007/tinkoffnews/ - исходники https://github.com/Arasthel/SwissKnife - можно посмотреть AST трансформации

    https://github.com/groovy/groovy-android-gradle-plugin https://github.com/ajoberstar/gradle-git https://github.com/ajoberstar/grgit