AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

Gradle plugin 3.3へのアップデート

先日Android Studio 3.3と共にGradle plugin 3.3.0がきたのでアップデートしてみた。

Crashlyticsを使っているとWarning

WARNING: API 'variant.getExternalNativeBuildTasks()' is obsolete and has been replaced with 'variant.getExternalNativeBuildProviders()'.
It will be removed at the end of 2019.
For more information, see https://d.android.com/r/tools/task-configuration-avoidance.
To determine what is calling variant.getExternalNativeBuildTasks(), use -Pandroid.debug.obsoleteApi=true on the command line to display a stack trace.
Affected Modules: app

android.debug.obsoleteApi=true をgradle.propertiesに追加するとstacktraceが見れる

WARNING: API 'variant.getExternalNativeBuildTasks()' is obsolete and has been replaced with 'variant.getExternalNativeBuildProviders()'.
It will be removed at the end of 2019.
For more information, see https://d.android.com/r/tools/task-configuration-avoidance.
REASON: It is currently called from the following trace:
java.lang.Thread.getStackTrace(Thread.java:1556)
com.android.build.gradle.internal.errors.DeprecationReporterImpl.reportDeprecatedApi(DeprecationReporterImpl.kt:79)
com.android.build.gradle.internal.api.BaseVariantImpl.getExternalNativeBuildTasks(BaseVariantImpl.java:487)
com.android.build.gradle.internal.api.ApplicationVariantImpl_Decorated.getExternalNativeBuildTasks(null:-1)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
groovy.lang.MetaBeanProperty.getProperty(MetaBeanProperty.java:59)
org.gradle.internal.metaobject.BeanDynamicObject$MetaClassAdapter.getProperty(BeanDynamicObject.java:228)
org.gradle.internal.metaobject.BeanDynamicObject.tryGetProperty(BeanDynamicObject.java:171)
org.gradle.internal.metaobject.CompositeDynamicObject.tryGetProperty(CompositeDynamicObject.java:55)
org.gradle.internal.metaobject.AbstractDynamicObject.getProperty(AbstractDynamicObject.java:59)
com.android.build.gradle.internal.api.ApplicationVariantImpl_Decorated.getProperty(null:-1)
org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49)
org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:299)
com.crashlytics.tools.gradle.ProjectVariantState.resolveDebugNativeLibsPath(ProjectVariantState.groovy:130)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:352)
groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:68)
org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:177)
com.crashlytics.tools.gradle.ProjectVariantState$_deriveFrom_closure1.doCall(ProjectVariantState.groovy:55)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)
groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
groovy.lang.Closure.call(Closure.java:418)
groovy.lang.Closure.call(Closure.java:434)
org.codehaus.groovy.runtime.DefaultGroovyMethods.with(DefaultGroovyMethods.java:327)
org.codehaus.groovy.runtime.dgm$758.invoke(null:-1)
org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoMetaMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:251)
org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.call(PogoMetaMethodSite.java:71)
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:128)
com.crashlytics.tools.gradle.ProjectVariantState.deriveFrom(ProjectVariantState.groovy:43)
com.crashlytics.tools.gradle.ProjectVariantState$deriveFrom.call(null:-1)
com.crashlytics.tools.gradle.CrashlyticsPlugin.createTaskBuilder(CrashlyticsPlugin.groovy:271)
com.crashlytics.tools.gradle.CrashlyticsPlugin.this$2$createTaskBuilder(CrashlyticsPlugin.groovy:-1)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)
org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
com.crashlytics.tools.gradle.CrashlyticsPlugin.addPluginTasks(CrashlyticsPlugin.groovy:245)
com.crashlytics.tools.gradle.CrashlyticsPlugin.this$2$addPluginTasks(CrashlyticsPlugin.groovy:-1)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
org.gradle.internal.metaobject.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:479)
org.gradle.internal.metaobject.BeanDynamicObject.tryInvokeMethod(BeanDynamicObject.java:191)
org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:78)
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeOnDelegationObjects(ClosureMetaClass.java:398)
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:338)
groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:68)
org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:185)
com.crashlytics.tools.gradle.CrashlyticsPlugin$_addPluginTasksToApplicationVariantsIn_closure2.doCall(CrashlyticsPlugin.groovy:92)
sun.reflect.GeneratedMethodAccessor2168.invoke(null:-1)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)
groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
groovy.lang.Closure.call(Closure.java:418)
groovy.lang.Closure.call(Closure.java:434)
org.gradle.api.internal.ClosureBackedAction.execute(ClosureBackedAction.java:71)
org.gradle.util.ConfigureUtil.configureTarget(ConfigureUtil.java:155)
org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:106)
org.gradle.util.ConfigureUtil$WrappedConfigureAction.execute(ConfigureUtil.java:167)
org.gradle.internal.ImmutableActionSet$SetWithFewActions.execute(ImmutableActionSet.java:285)
org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:244)
org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:233)
com.android.build.gradle.AppExtension.addVariant(AppExtension.java:87)
com.android.build.gradle.internal.ApiObjectFactory.create(ApiObjectFactory.java:134)
com.android.build.gradle.BasePlugin.createAndroidTasks(BasePlugin.java:777)
com.android.builder.profile.ThreadRecorder.record(ThreadRecorder.java:81)
com.android.build.gradle.BasePlugin.lambda$createTasks$4(BasePlugin.java:651)
com.android.build.gradle.internal.crash.CrashReporting$afterEvaluate$1.execute(crash_reporting.kt:37)
com.android.build.gradle.internal.crash.CrashReporting$afterEvaluate$1.execute(crash_reporting.kt:-1)
org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction$1$1.run(DefaultListenerBuildOperationDecorator.java:155)
org.gradle.configuration.internal.DefaultUserCodeApplicationContext.reapply(DefaultUserCodeApplicationContext.java:58)
org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction$1.run(DefaultListenerBuildOperationDecorator.java:152)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction.execute(DefaultListenerBuildOperationDecorator.java:149)
org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:91)
org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:80)
org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:42)
org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:230)
org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:149)
org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:58)
org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:324)
org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:234)
org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:140)
org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
com.sun.proxy.$Proxy27.afterEvaluate(null:-1)
org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:187)
org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:184)
org.gradle.api.internal.project.DefaultProject.stepEvaluationListener(DefaultProject.java:1418)
org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:193)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:110)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:68)
org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:687)
org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:140)
org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:35)
org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:62)
org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:41)
org.gradle.initialization.DefaultGradleLauncher$ConfigureBuild.run(DefaultGradleLauncher.java:274)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
org.gradle.initialization.DefaultGradleLauncher.configureBuild(DefaultGradleLauncher.java:182)
org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:141)
org.gradle.initialization.DefaultGradleLauncher.getConfiguredBuild(DefaultGradleLauncher.java:119)
org.gradle.internal.invocation.GradleBuildController$2.call(GradleBuildController.java:86)
org.gradle.internal.invocation.GradleBuildController$2.call(GradleBuildController.java:83)
org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:154)
org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:38)
org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:96)
org.gradle.internal.invocation.GradleBuildController.configure(GradleBuildController.java:83)
org.gradle.tooling.internal.provider.runner.ClientProvidedBuildActionRunner.run(ClientProvidedBuildActionRunner.java:70)
org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.run(RunAsBuildOperationBuildActionRunner.java:50)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:45)
org.gradle.tooling.internal.provider.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:51)
org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:47)
org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:44)
org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:79)
org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:44)
org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:39)
org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:25)
org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:80)
org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:53)
org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:62)
org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:34)
org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43)
org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29)
org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:59)
org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:44)
org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:46)
org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:30)
org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
org.gradle.util.Swapper.swap(Swapper.java:38)
org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:62)
org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:81)
org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:122)
org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:295)
org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
java.lang.Thread.run(Thread.java:745)
WARNING: Debugging obsolete API calls can take time during configuration. It's recommended to not keep it on at all times.
Affected Modules: app

どうやらCrashlyticsを使うときに必要な apply plugin: ‘io.fabric' が原因の模様。Warningだし、その内直るだろうということで許容する。

stackoverflow.com

起動直後にクラッシュ

 java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.hoge. Make sure to call FirebaseApp.initializeApp(Context) first.
        at com.google.firebase.FirebaseApp.getInstance(com.google.firebase:firebase-common@@16.0.2:240)
        at com.google.firebase.iid.FirebaseInstanceId.getInstance(Unknown Source:1)

classpath 'com.google.gms:google-services:4.2.0’ にアップデートすることで解決

Gradle 5.1.1にアップデート

Gradle 5.1からAndroid Studio 3.3でも使えるようになったのでアップデートする。

If you are using Gradle for Android, you need to move to version 3.3 or higher of both the Android Gradle Plugin and Android Studio.

docs.gradle.org

※Gradle 5.0まではAndroid Studio 3.4以上だった

If you are using Gradle for Android, you need to move to version 3.4 or higher of both the Android Gradle Plugin and Android Studio.

docs.gradle.org

どうやらGradle 5.1だとバグがあるようなのでGradle 5.1.1を使うように注意。

developer.android.com

./gradlew wrapper でjarも更新。

CIで./gradlew dependenciesがエラー

./gradlew dependencies は足りないSDKをインストールするという認識だったがいつからか dependencies - Displays all dependencies declared in root project 'SampleProject' となっていた。そもそもGradle plugin 2.2.0からは自動で足りないSDKをダウンロードしてくれるのでやる必要がなさそう... 注意として、Gradle plugin 3.3からはNDKも自動ダウンロードの範囲に入ったので古いNDKを使っている場合は先にダウンロードしておかないと最新版が入ってしまう。

Error: This view is not constrained horizontally: at runtime it will jump to the left unless you add a horizontal constraint [MissingConstraints]

lintでContraintLayoutの横の制約がきちんと設定されていないとエラーになるようになったので設定する

個人アプリのAndroidX対応時のメモ

Support Libraryに依存した3rd party libraryが入っていないせいかあっさり終わった。

移行前の環境

  • Android Studio 3.2.1
  • compileSdkVersion 28
  • targetSdkVersion 28
  • Support Library 28.0.0

Refactor -> Migrate to AndroidX

ほぼこれで終わる。ただこれだけではビルドが通らないので修正する。

コンパイルエラーの修正

Non-Nullが返ってきたり型指定が必須になるので直す。

  • override fun onAttachFragment(fragment: Fragment?) -> override fun onAttachFragment(fragment: Fragment)
  • override fun onAttach(context: Context?) -> override fun onAttach(context: Context)
  • progressDialog.show(activity?.supportFragmentManager, null) ->
activity?.supportFragmentManager?.let {
    progressDialog.show(it, null)
}
  • findPreference() -> findPreference<Preference>()

リファクタリング

ツールの不具合かimportしているのにクラスを直指定しているところがあったので修正

  • RecylcerView周りの androidx.recyclerview.widget を消す
  • androidx.fragment.app.Fragment -> Fragment
  • androidx.swiperefreshlayout.widget.SwipeRefreshLayout -> SwipeRefreshLayout

クラッシュ修正

ビルドが通るようになったが実行時クラッシュがあった。

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.sample/com.sample.HogeActivity}: android.view.InflateException: Binary XML file line #23: Binary XML file line #23: Error inflating class com.google.android.material.textfield.TextInputLayout
     Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.MaterialComponents (or a descendant).

どうやら新しい TextInputLayout はマテリアルのテーマが必要なようなので、アプリのテーマを Theme.MaterialComponent にするかTextInputLayoutにstyle="@style/Widget.MaterialComponents.FloatingActionButton” を適用する。

FloatingActionButtonの色が変わる問題の修正

どうやらデフォルトの背景色がcolorAccentからcolorSecondaryに変わり、アイコンの色(tint)がcolorOnSecondaryに変わった模様。必要であれば色を指定する。

おわりに

以外とあっさり終わった。Support Libraryに依存しているライブラリが入っているとその対応を待ったり、下手したらライブラリの置き換えも考慮しなければいけないと思う。あとMigrate to AndroidXするとalphaだろうが最新のバージョンを指定してくるので会社のプロダクトではバージョンを下げたほうが良さそう。

Google Home+Nature Remo+IFTTTを使って音声でテレビの入力を切り替える

今リビングで使っているテレビでは入力1がPS4、入力2がChromecast、入力3がNintendo Switchとなっている。テレビは基本的にPS4のトルネで見ている。 よくChromecastでYoutubeを見たりすると入力が切り替わるのだが、Chromecastに入力切替の操作が奪われてしまい(?)、PS4に戻すにはチャンネル変更で操作を奪い返し、ホーム→入力切替としなければいけないので結構面倒くさい(でかいリモコンを使えばもっと楽にできるかもしれない)。この操作をGoogle Home経由でできるようにした(ついでにAndroidウィジェットからも)。

※この記事では全ての機器、サービスの初期設定が終わっている前提です。

環境

機器

アプリ

  • Google Home 2.7.21.2
  • Nature Remo 2.7.0(168)
  • IFTTT3.7.4

Nature Remoアプリでシーンを作る

最近Nature Remoアプリではシーンという複数の家電・操作をまとめて1アクションにできる機能が増えた。 この機能を使って入力切替を行うシーンを作る。

f:id:phicdy:20181202163806p:plain

IFTTTアプリでGoogle Home → Nature Remoの連携する

My Applets → 「+」 で新規作成開始。 thisにGoogle Assistantを選択。 Triggerを「Say a simple phrese」にし、「What do you want to say ?」に「クロームキャストからプレステ」等設定する。LanguageをJapaneseにする。

f:id:phicdy:20181202163521p:plain

thatでNature Remoを選択。「Execute scene」で先程作った「1つ上の機器に切り替える」を選択

f:id:phicdy:20181202164037p:plain

これで設定は完了です。Google Homeに「OK, Google。クロームキャストからプレステ」と言えば入力が切り替わる。

Androidウィジェットの設定

Nature Remoアプリを開けばシーンを実行できるが、面倒なのでウィジェットとしてホーム画面に置きたい。 IFTTTアプリでウェジェットが作れるのでITTTTアプリを開いてMy Applets → 「+」 で新規作成開始。 thisにButton widgetを選択。thatでNature Remoを選択。「Execute scene」で先程作った「1つ上の機器に切り替える」を選択。

これでスマホのホーム画面にウィジェットを追加できる。

おわりに

正直なところを言えば「プレステに変えて」でどの入力を表示していたとしても切り替えられるようにしたい。しかしテレビ入力の現在の状態を取得する方法がないため、「クロームキャストからプレステ」で1個上に切り替える、「スイッチからプレステ」で2個上に切り替えるという風に設定して人間が現在の状態を判断してアクションを実行しないといけない。実現するにはテレビ側が対応するかNature Remoが状態を持っていないといけないのでなかなか難しそう・・・。あとPS4の操作もできるようにならないかなあと思った。家についたらテレビとPS4をつけて入力をPS4に戻してトルネ起動くらいまで自動化したい。

FragmentでViewPagerを使うときにFragmentPagerAdapterに渡すFragmentManagerの注意点

嵌ったのでメモ。

BottomNavigationBar のタブの Fragment 内で ViewPager を使っていて、そのタブから別タブに切り替えて再びそのタブに戻ってきたとき、FragmentPagerAdapter#getItem() が呼ばれず、 ViewPager 内が再読込みされない現象が起きた。

調査の結果、 FragmentPagerAdapterインスタンスを作るときに渡す FragmentManagerFragmentActivity#getSupportFragmentManager() だと起きる。

そもそも FragmentPagerAdapter はメモリ上に全てViewPagerの子Fragment達を保持する。そのためタブを切り替えて戻ってきたときには以前の Fragment が使い回される。

FragmentPagerAdapter  |  Android Developers

実際に FragmentPagerAdapter#getItem() が呼ばれる付近のコードを見てみると確かに Fragment が保存されている場合は FragmentPagerAdapter#getItem() が呼ばれない。

public abstract class FragmentPagerAdapter extends PagerAdapter {
    ...
    
    @NonNull
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        long itemId = this.getItemId(position);
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = this.mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
            this.mCurTransaction.attach(fragment);
        } else {
            fragment = this.getItem(position);
            this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
        }

        if (fragment != this.mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }
    ...
}

BottomNavigationView のタブ切り替えでは Activity は生きたままなのでその FragmentManagerViewPager内の子Fragment達を保持したままになり、 FragmentPagerAdapter#getItem() が呼ばれない。


↓の回答を見ると Fragment#getChildFragmentManager()FragmentPagerAdapterに渡すといいとある。 stackoverflow.com

この場合、保持される ViewPager 内の子Fragment達はその親FragmentFragmentManagerで管理される。タブを切り替えたときには通常、OnNavigationItemSelectedListener#onNavigationItemSelected()で親Fragmentの新しいインスタンスを作るので、FragmentManagerもリセットされ、 FragmentPagerAdapter#getItem() が呼ばれて再読込みが始まるようになる。

ViewPager のスワイプが起きたときは 親 Fragment が生きている限り、 FragmentManager 内で子 Fragment 達が管理されるので、通常通り効率的にスワイプできるようになる。

ちなみにこの問題は子Fragmentをメモリ上に保持しないFragmentStatePagerAdapterでは発生しない。これはページ数が多いなどメモリ保持するには重い場合に使う。

FragmentStatePagerAdapter  |  Android Developers

↑StackOverflowの回答の中で一番+が多い案は FragmentStatePagerAdapterを使う案である。この場合はメモリ上に子Fragmentを保持しないのでスワイプ時の挙動が変わる。どちらが良いかはページ数などの状況で決めるといいと思う。

【Android】続・PreferenceFragmentCompatを使った設定画面

以下の記事を昔に書いたが、内容が古くなってきたようなので書き直す。

phicdy.hatenablog.com

Androidの設定画面について

  • AndroidではAPI1から設定画面を生成してくれる PreferenceActivity が用意されている
  • Android 3.0で各設定画面をFragmentに分けるための PreferenceFragment が追加された
  • Android 4.0では PreferenceActivity が拡張され、 PreferenceFragment と組み合わせることでハンドセットでは1画面、タブレットでは2画面の設定画面を自動的に作成できる
  • API 28で PreferenceFragment はdeprecatedになり PreferenceFragmentCompat が推奨となった。PreferenceActivity も使う必要はなく、 AppCompatActivity を使用する。

準備

PreferenceFragmentCompat を使用するためにはサポートライブラリが必要。AndroidX版もあるがひとまず28.0.0で書く。

    def supportLibVer = '28.0.0'
    implementation "com.android.support:appcompat-v7:$supportLibVer"
    implementation "com.android.support:preference-v7:$supportLibVer"

テレビ用には preference-leanback-v17 があるがここでは説明しない。

設定項目

クラス 説明
v7.preference.CheckBoxPreference オン/オフの設定項目
v14.preference.SwitchPreference オン/オフの設定項目
v7.preference.ListPreference リストの中から1つ選ぶ設定項目
v14.preference.MultiSelectListPreference リストの中から複数を選ぶ設定項目
v7.preference.EditTextPreference 入力からの設定

設定画面

Activity

class SettingsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .beginTransaction()
                .replace(android.R.id.content, SettingsFragment())
                .commit()
    }
}

PreferenceFragmentCompat

  • onCreatePreferences()addPreferencesFromResource() から設定画面を定義したXMLを読み込む
  • 必要であれば各設定項目の初期値等を設定する
  • XMLで設定した Preference を取得するには android:key を使い、PreferenceFragmentCompat#findPreference() で行う
class SettingFragment : PreferenceFragmentCompat() {

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        addPreferencesFromResource(R.xml.setting_fragment)
    }
}

ヘッダー

<preference-header> によるヘッダー作成から<Preference android:fragment=""> で指定するようになった。設定するだけでは遷移してくれず、 Activity 側で PreferenceFragmentCompat.OnPreferenceStartFragmentCallback を実装して onPreferenceStartFragment() でハンドリングしてあげる必要がある。

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- These settings headers are only used on tablets. -->

    <header
            android:fragment="com.phicdy.preferencesample.GeneralPreferenceFragment"
            android:title="@string/pref_header_general"
            android:icon="@drawable/ic_info_black_24dp"/>

    <header
            android:fragment="com.phicdy.preferencesample.NotificationPreferenceFragment"
            android:title="@string/pref_header_notifications"
            android:icon="@drawable/ic_notifications_black_24dp"/>

    <header
            android:fragment="com.phicdy.preferencesample.DataSyncPreferenceFragment"
            android:title="@string/pref_header_data_sync"
            android:icon="@drawable/ic_sync_black_24dp"/>

</preference-headers>

<PreferenceScreen
        xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- These settings Preferences are only used on tablets. -->

    <Preference
            android:key="aaa"
            android:fragment="com.phicdy.preferencesample.GeneralPreferenceFragment"
            android:title="@string/pref_header_general"
            android:icon="@drawable/ic_info_black_24dp"/>

    <Preference
            android:key="bbb"
            android:fragment="com.phicdy.preferencesample.NotificationPreferenceFragment"
            android:title="@string/pref_header_notifications"
            android:icon="@drawable/ic_notifications_black_24dp"/>

    <Preference
            android:key="ccc"
            android:fragment="com.phicdy.preferencesample.DataSyncPreferenceFragment"
            android:title="@string/pref_header_data_sync"
            android:icon="@drawable/ic_sync_black_24dp"/>

</PreferenceScreen>

設定画面でそんなに需要があるのかわからないが、切替時に処理を入れたり、アニメーションを入れることができる。

class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

    // AndroidX版だとNon-Null
    override fun onPreferenceStartFragment(caller: PreferenceFragmentCompat?, pref: Preference?): Boolean {
        // AndroidX版だとval fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, pref.fragment, args) で書ける
        val fragment = when (pref?.fragment) {
            GeneralPreferenceFragment::class.java.name -> GeneralPreferenceFragment()
            NotificationPreferenceFragment::class.java.name -> NotificationPreferenceFragment()
            DataSyncPreferenceFragment::class.java.name -> DataSyncPreferenceFragment()
            else -> throw InvalidParameterException("Invalid fragment, fragment name is " + pref?.fragment)
        }
        fragment.setTargetFragment(caller, 0)

        supportFragmentManager.beginTransaction()
            .replace(android.R.id.content, fragment)
            .addToBackStack(null)
            .commit()
        return true
    }
}

PreferenceFragmentCompatから読み込む設定XML

  • PreferenceFragmentCompat から読み込むXMLのトップは PreferenceScreen でなければならない
  • Preference には android:key を設定する。これが各 Preference を特定するIDとなる。
  • カテゴリを設定する場合は PreferenceCategory を使う。 android:key は必須ではない。
  • ListPreference の場合、選択候補に表示されるリスト( android:entries )と実際の値のリスト( android:entryValues )を設定する必要がある
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory android:title="Category Title" >
        <ListPreference
            android:title="Title"
            android:key="@string/key_list_preference"
            android:entries="@array/entry_sample"
            android:entryValues="@array/entry_sample_values"/>
    </PreferenceCategory>

</PreferenceScreen>

その他の属性は以下を参照

http://developer.android.com/reference/android/preference/Preference.html

array.xml

ListPreference で使う値を定義する

<resources>
    <array name="entry_sample">
        <item>@string/entry1</item>
        <item>@string/entry2</item>
    </array>

    <string-array name="entry_sample_values">
        <item>1</item>
        <item>0</item>
    </string-array>

</resources>

初期値設定

XMLで初期値を設定する場合はandroid:defaultValueに設定する。SharedPreference から保存した値を読み込んでそれに応じて初期値を設定する場合など、コードの中で初期値を設定を行う場合は Preference によって設定方法が異なる

SwitchPreference

setChecked() で行う

val switchPref = findPreference(getString(R.string.key_internal_browser)) as SwitchPreference
switchPref.setChecked(true)

ListPreference

  • ListPreferenceentryValuesstring-array の各itemが数字だと setDefaultValue() が動かなかったので setValueIndex() を使うことで解決
  • entryValuesinterger-array にすると NullPointerException で落ちてしまう

イベントハンドリング

Preference.OnPreferenceChangeListenerSharedPreferences.OnSharedPreferenceChangeListener の2種類がある。 Preferennce はデフォルトで値を SharedPreferences に保存する。 PreferenceDataStore を使うことで好きな場所に保存することもできる。

Preference.OnPreferenceChangeListener は値が変わる前に呼ばれるのでバリデーションなどに使う。 PreferenceDataStore を使う場合はここでハンドリングして値を保存する。 SharedPreferences.OnSharedPreferenceChangeListenerSharedPreferences のみ使用でき、値が保存された後に呼ばれる。

参考

Clean ArchitecutureにおけるKotlin coroutinesの処理と責務分け

自分の中で混乱してきたので整理する。

環境

  • Kotlin 1.2.71
  • Kotlin Coroutines 0.30.1

本題

以下のようなClean Architecuteベースの設計のアプリがあるとする。

f:id:phicdy:20181011225951p:plain

このときKotlin Coroutineを使って非同期処理を行ってUIを更新するとする。 各クラスの責務を分けると以下のようになる。

クラス 役割 やらない・知らない スレッド
Activity/Fragment UIを更新する。インターフェースをPresenterに提供し、イベントの処理をPresenterに移譲する。 UIロジックを持たない。Presenterが何をするかは知らない UIスレッド
Presenter UseCaseに処理を依頼し処理結果を待つ。その結果に応じてUIを更新する UIの実態を知らない。UseCaseがどう処理を実行しているか知らない UIスレッド
UseCase Repository/Apiからデータを取得して処理を行う Repository/Apiがどこからデータを取得しているか知らない。 UIスレッド/ワーカースレッド
Repository/Api 外部・内部からデータを取得する 返したデータがどう使われるかは知らない ワーカースレッド

PresenterはUseCaseがどう処理を実行するか知らないし、UseCaseもRepository/Apiがどう処理をするかを知らない。めちゃくちゃ早いCPUで処理をメインスレッド上で5秒以内に実行しているかもしれないし、ワーカースレッドで待ち合わせをして結果を返しているかもしれない。現実的にはUseCaseがRepository/Apiの結果を待ち合わせをし、処理を行って結果を返す。

Activity/FragmentはAndroid SDKに基づくUIスレッドで実行するコルーチンビルダーを呼ぶ。

class MyActivity: AppCompatActivity(), MyView, CoroutineScope {

    private lateinit var presenter: MyPresenter
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCreate() {
        presenter = MyPresenter(this)
        launch(context = coroutineContext) {
            presenter.onCreate()
        }
    }
    
    override fun showSuccess() {
        ...
    }
    
    override fun showFail() {
        ...
    }
}

Presenterでは呼び出し元のCoroutineScopeで処理を実行し、結果に応じてUIを更新する。UseCaseがどのスレッドで処理をしているかは意識しない。

class MyPresenter(private view: MyView) {
    suspend fun onCreate() = corountineScope {
        val result = useCase.fetchAndExecute()
        if (result.isSuccess()) {
            view.showSuccess()
        } else {
            view.showFail()
        }
     }
}

UseCaseは中でRepositoryからデータを取得する。Repositoryがどのスレッドで処理をしているかは意識しない。呼び出し側のUseCaseは非同期を意識せずに同期的に書くことができる。返ってきた結果を別メソッドで処理するが、ここでもスレッドを意識しない。実際には withContext() でワーカースレッド上で処理される。

class MyUseCase {
    suspend fun fetchAndExecute(): Result = corountineScope {
        val data = repository.fetchData()
        return onFetch(data)
    }
    
    private suspend fun onFetch(data: List<Model>): Result = coroutineScope {
        return@coroutineScope withContext(Dispatchers.Default) {
                // なんか重たい処理
                return@withContext ...
            }
        }
    }
}

Repository/Apiでは withContext() でIOスレッドに切り替えて処理を返す。

class MyRepository {
    suspend fun fetchData(): List<Model> = coroutineScope {
        return@coroutineScope withContext(Dispatchers.IO) {
            // データベース等からなにかを取得する処理
            return@withContext ...
        }
    }
}

Presenterのテスト

テストではUseCaseとViewをモックし、 runBlocking 内で実行することでAndroid SDKからの依存をなくし、同期的に実行する。

@Test
fun `when fetch and execute succeeds then show success`() = runblocking { 
    val view = mock(MyView::class.java)
    val useCase = mock(MyUseCase::class.java)
    val presneter = MyPresneter(view)
    `when`(useCase.fetchAndExecute()).thenReturn(...)
    presenter.useCase = useCase
    presenter.onCreate() }
    verify(view, times(1)).showSuccess()
}

おわりに

0.26.1の破壊的変更でいろいろ調べていたら時間がかかってしまった。もうそろそろKotlin 1.3が出てCoroutinesが1.0になる。また破壊的変更が来ないことを願う。

Kotlin Fest 2018に参加した

2018/8/25に品川で開催されたKotlin Fest 2018に参加してきました。

kotlin.connpass.com

f:id:phicdy:20180825132036j:plain

f:id:phicdy:20180825132200j:plain

飲み物とか食事。パンプディング美味しかった。

f:id:phicdy:20180825132327j:plain

f:id:phicdy:20180825132720j:plain

mixiさんブースでやってKotlinクイズの景品。7問中3問正解でプランニングポーカーもらいました。

f:id:phicdy:20180825132358j:plain

以下参加したセッションの感想

Kotlin で改善する Android アプリの品質

KotlinFest.pdf - Speaker Deck

非技術系の上の人にJava -> Kotlin を説得しにくいよねという話から始まり、Effective JavaにならってKotlinでの言語仕様での対応が聞けた。 kotlinにするだけで自然にEffective Javaのエッセンスを導入できる!というのが確かに・・・となってそれだけでもコストをかけて移行する価値があると感じた。Javaのつらみを書かなくていいし・・・。Effective Java読み直そう。

Kotlinアプリのリファクタリングポイント

Refactoring point of Kotlin application

nullが何を表しているかを考えることは重要だなと思った。状態数の話がまさに今直面している問題で、名前をつけたりすることを考えるきっかけになりそう

Kotlin linter

kotlin linter - Speaker Deck

カスタムルールは自然言語処理的なつらさがありそう。PsiViewerでだいぶ楽になる。カスタムフォーマットは更につらそう。android-lintが型が見れて強い。

Kotlin コルーチンを理解しよう

Kotlin コルーチンを 理解しよう - Speaker Deck

コルーチンを全然理解しないまま使っていたので、この発表でかなりすっきりした。実際にはステートマシンに変換されていることや直列・並列実行、待ち合わせなどがどう実現されているかの理解がかなり深まったと思う。

LT大会

3分短いなーと。個人的には5分くらいあってもよかったんじゃないかと。

おわりに

まずKotlinやっている人こんなにいるんだな〜というのが感想だった。Androidだけじゃなくサーバサイドの人も増えてきている印象だった。Java -> Kotlinに変換する話題が多かったのはなんだろう・・・まだ移行期で完全に移行できていない、もしくは上の人を説得できなくてJavaのままのプロダクトがまだまだあるのかなと感じました。 2019はまだ未定とのことですが、どのセッションも面白い話が多くて今日からでも使えそうな考え方や知見が得られたのでぜひ来年もやってほしい!

会場設備もよく、飲み物や軽食が充実していた。これだけの人数だと仕方ないけど立ち見はつらそうだった。 わいわいした雰囲気で楽しかったので、2019があればまた参加しようと思う。