Kotlin Evolves: How to Keep Your Code Up

Kotlin is undergoing finalization, and as part of the process we are cleaning up: revising the language and its libraries. The biggest changes have been made in M12, but some more are coming. The point is to perform all the necessary breaking changes before we release 1.0, so that we can keep the language and libraries backwards-compatible after the release.

The trick is both we, ourselves, and you, our users, have quite a bit of code written in Kotlin already, and we don’t want all that code broken hopelessly on each update (some breakages are inevitable, unfortunately, but we are doing our best). The general scheme of making changes in a user-friendly way is “deprecate-release-remove”, for example:

  • in M12 we deprecated quite a few language constructs and library classes/functions,
  • then we released M12, so that whenever you use those to-be-removed language and library features, the compiler issues warnings,
  • in the next milestone we will remove those deprecated things completely, so that the compiler will issue errors instead of warnings.

So, if you have any deprecation warnings in your code, now is just the right time to get rid of them: the next major update will make all that code red, and your build will break.

Getting rid of deprecation warnings

As mentioned above, there are two kinds of deprecation warnings: language deprecations and library deprecations. To get rid of them we provide several options.

Quick Fixes

With a Quick Fix you can fix a deprecation warning when you press Alt+Enter on it: available options will appear that fix an individual warning or all such warnings in the entire project. Deprecated language constructs and library functions will be replaced by newer versions.

before_qf


Before applying deprecated syntax quick fix

after_qf


After applying deprecated syntax quick fix

Please note that for the quick fix to work correctly on library functions, the sources of the standard library must be attached to your project.

Project-Wide Code Cleanup

Quick fixes are good when you have located the deprecated usage already, but since we have deprecated a lot of things recently, it may not be a viable option to look for each individual usage. In such a case, you can apply these fixes in bulk with an IDE action “Analyze | Code Cleanup“.

Kotlin Code Cleanup Profile

Kotlin Code Cleanup Profile

We provide an inspection named “Usage of redundant or deprecated syntax or deprecated symbols“. During the Cleanup, this inspection automatically replaces usages of obsolete language features or unnecessarily verbose code constructs with compact and up-to-date syntax, and usages of deprecated symbols — with their proposed substitutions.

Manual Rewrite

In rare cases some deprecated usages can not be fixed automagically. These include usages of language constructs and library symbols that will be dropped without alternatives or can’t be rewritten without breaking the code, or when there are several options of rewriting the code, so that explicit choice is required.

Bottom line. How to get ready for Kotlin M13:
– install Kotlin M12 (ensure you have latest update of M12 installed),
– get rid of the deprecation warnings by means described above.

Which API’s will definitely be dropped in M13

We’d like to share some of our plans about the API to be dropped in M13. This is not an exhaustive list of what will be dropped, but nevertheless.

Streams

The big problem with Kotlin streams is that they conflict (by names) with streams in Java 8, and we can’t rely on Java 8 streams, because Kotlin targets earlier JDKs as well (think Android). So, sequences were introduced in M11 instead of streams, and streams are to be dropped.

Iterator Utilities

Utility functions for iterators and some iterator classes were deprecated in favor of streams in M8, but streams are deprecated now too, so the ultimate inheritor of such functionality is Sequence.

Sequence implementations

We have a bunch of sequence implementations in the Standard Library required to implement sequence operations, for example, FilteringSequence for sequence.filter { predicate }, TransformingSequence for sequence.map { transform } and so on. These implementation classes do not make much sense as part of the public API of the library, so we have deprecated them in M12 and are making them private in M13 (or dropping, from the user’s point of view :)).

String.split and String.replaceFirst

Prior to M12 we had extension methods on string inherited from JDK: split, replaceAll/replaceFirst, matches. What they all have in common is that they take a string parameter and interpret it as a regular expression. This approach has two disadvantages.

Firstly, it requires compilation to a Pattern under the hood, which may have performance implications when used in a tight loop. This is explained in detail an article named “Hidden evils of Java’s String.split() and replace()”. We want to make every effort to avoid having articles named “Hidden evils of Kotlin’s something…” written in the future, so we decided, as usual, to make things explicit and introduced overloads of these functions that take Regex as a parameter where a regular expression is expected.

Secondly, it makes it difficult to introduce an overload taking a string and interpreting it as a literal. Willing to introduce such overloads in M12 for split and replaceFirst, we had to name them splitBy and replaceFirstLiteral respectively. But as soon as we drop the original deprecated overloads in M13, we’re going to rename these new methods back to split and replaceFirst (of course we’ll leave splitBy and replaceFirstLiteral deprecated and provide a substitution in their deprecated annotation).

It is also worth noting that the return type of split method has been changed: it now returns a List<String> instead of Array<String>, and the change of its behavior regarding removing empty substrings in the end. Following the principle of the least surprise we have relieved split from this inappropriate responsiblity, and nowinstead of split(regex: String) we should say split(regex.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray(). The IDE will help you migrate.

Conclusion

We are going to make more changes in the Standard Library, but we have you covered with the code cleanup and quick fixes. Performing the code cleanup as described above for each Kotlin milestone before release will keep your code up to date.

This entry was posted in Libraries. Bookmark the permalink.

20 Responses to Kotlin Evolves: How to Keep Your Code Up

  1. Mohammad Shamsi says:

    Any plan to deprecate “constructor” and use Java-like constructor names ?

    • No, it looks like everyone is pretty happy with constructor (maybe almost everyone :))

      • Mohammad Shamsi says:

        Well, I don’t have any strong argument against it, but I don’t see any value there too. So if the new way “constructor” is not bringing any value, why don’t just use the old way? at least developers are used to it.

  2. Dmitry says:

    Hello! What should be used instead sequence.map {} and sequence.filter {} ?
    (I didn’t get why those make no sense, I thought that they complement similar operations on iterables)

  3. Justin Johansson says:

    Sounds like a good plan as you head towards a frozen language design in Kotlin 1.0. I do hope however that you will allow ample time between “0.9” and “1.0” as a window of opportunity for late-coming community feedback and make plenty of “last call” announcements before the 1.0 language design freeze.

  4. Morj says:

    As I can see, sequence.map will not be dropped, only the corresponding TransformingSequence will be made private.

  5. Julys Pablo says:

    Any plans to provide support to new Android Data Bind Library? I’ve tried use it with Kotlin but I failed.

    • Depends on what you mean by “failed”: if you had an exception from your build, just update your build to the newest Kotlin available in Maven Central. If it’s anything else, please share your experience. Thanks

      • Julys Pablo says:

        I’ve found the following exception:

        FAILURE: Build failed with an exception.

        What went wrong:
        Execution failed for task ‘:app:compileDebugJavaWithJavac’.
        > java.lang.RuntimeException: failure, see logs for details.
        cannot generate view binders java.lang.NoClassDefFoundError: kotlin/jvm/internal/ExtensionFunctionImpl
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
        at android.databinding.tool.writer.DataBinderWriter.write(DataBinderWriter.kt:20)
        at android.databinding.tool.CompilerChef.writeDbrFile(CompilerChef.java:65)
        at android.databinding.annotationprocessor.ProcessExpressions.writeResourceBundle(ProcessExpressions.java:134)
        at android.databinding.annotationprocessor.ProcessExpressions.generateBinders(ProcessExpressions.java:97)
        at android.databinding.annotationprocessor.ProcessExpressions.onHandleStep(ProcessExpressions.java:72)
        at android.databinding.annotationprocessor.ProcessDataBinding$ProcessingStep.runStep(ProcessDataBinding.java:107)
        at android.databinding.annotationprocessor.ProcessDataBinding$ProcessingStep.access$000(ProcessDataBinding.java:93)
        at android.databinding.annotationprocessor.ProcessDataBinding.process(ProcessDataBinding.java:61)
        at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:793)
        at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:722)
        at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1700(JavacProcessingEnvironment.java:97)
        at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1029)
        at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1163)
        at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1108)
        at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:824)
        at com.sun.tools.javac.main.Main.compile(Main.java:439)
        at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:132)
        at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:45)
        at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:33)
        at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.delegateAndHandleErrors(NormalizingJavaCompiler.java:101)
        at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:50)
        at org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler.execute(NormalizingJavaCompiler.java:36)
        at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:34)
        at org.gradle.api.internal.tasks.compile.CleaningJavaCompilerSupport.execute(CleaningJavaCompilerSupport.java:25)
        at org.gradle.api.tasks.compile.JavaCompile.performCompilation(JavaCompile.java:157)
        at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:137)
        at org.gradle.api.tasks.compile.JavaCompile.compile(JavaCompile.java:91)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
        at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.doExecute(AnnotationProcessingTaskFactory.java:243)
        at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:219)
        at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$IncrementalTaskAction.execute(AnnotationProcessingTaskFactory.java:230)
        at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:208)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
        at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
        at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
        at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:42)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
        at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
        at org.gradle.api.internal.AbstractTask.executeWithoutThrowingTaskFailure(AbstractTask.java:310)
        at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.executeTask(AbstractTaskPlanExecutor.java:79)
        at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:63)
        at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:51)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:23)
        at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:88)
        at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
        at org.gradle.execution.DefaultBuildExecuter.access$200(DefaultBuildExecuter.java:23)
        at org.gradle.execution.DefaultBuildExecuter$2.proceed(DefaultBuildExecuter.java:68)
        at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:62)
        at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:55)
        at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:149)
        at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:106)
        at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:86)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:90)
        at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:54)
        at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
        at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:49)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
        at org.gradle.util.Swapper.swap(Swapper.java:38)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:66)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:71)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
        at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
        at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:246)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
        at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)
        Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.ExtensionFunctionImpl
        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
        … 109 more

        • Alexander Udalov says:

          Unfortunately, Android Data Binding is currently using Kotlin M11 itself, which is incompatible with M12, so the only solution is to wait until it updates to Kotlin M12.

          • Mykola says:

            I have similar issue on latest databindings lib which uses M12 under the hood. My Android Studio just hangs when i trying to convert java->kotlin with some bindings implemented

  6. jsbr says:

    I have a request for your change in Standard Library.

    Please remove or change package of any function like to(…) and other global extension method that affect all object . These functions can be confusing and can mask problems. For example in dependency injection a standard code likes (silk di):

    bind().to(1)

    Is valid and do not throw any exception at runtime because it use the global function and not the implemeted function.

    P.S: Sorry for my English

  7. Marcel says:

    Are there any plans to implement Regex as a language feature like in JS or Perl? Would this have a big impact on compile time?

Comments are closed.