How to move a function from one Kotlin class to another using IntelliJ? - intellij-idea

I'm using IntelliJ IDEA to refactor some Kotlin code. I have two classes in the same file and I want to move a function from one class to another using Refactor -> Move (F6), but that doesn't work, and I get tooltip message that says: "Cannot perform refactoring. Move declaration is only supported for top-level declarations and nested classes".
Am I doing something wrong? Or that refactoring is simply not supported?
[edit1] I tried to do the same operation with Java classes and everything works perfectly; so why this is not allowed for Kotlin?
[edit2] I thought that the problem is only when to two classes are in the same file, but it turns out that is not possible to move a function between classes in separate files!

It's a well-known Kotlin-only problem.
in IDEA (both free and paid editions);
in Android Studio.
Official ticket

There is an easy, but slightly janky, work around.
You just need to wrap the function you want to move in a class:
class TopLevelClass {
fun functionToMove() {
//...
}
}
wrap it in a new class
class TopLevelClass {
class TemporaryMoveClass{ /** you can now move this entire new class */
fun functionToMove() {
//...
}
}
}
and after you do the refactor, delete the temporary wrapper class you created.
The janky part is that you need to replace all instances of functionToMove() with NewTopLevelClass.functionToMove() yourself.
One of the major benefits of doing it this way, rather than just cut and pasting it yourself, is that as soon as you wrap it in the TemporaryMoveClass it will tell you any parameters you need to introduce(Refactor>Extract>Parameter). And then you can do that inside the original TopLevelClass before you move it. (this preserves the types of any TopLevelClass properties you were using, and automatically introduces the new parameter(s) into the existing function calls)

Related

IntelliJ type error when using Geb static content DSL with parameters

Whenever I use a static-content defined closure that takes parameters, IntelliJ will complain that the types do not match (even if I specify the type inside the closure).
For example, if I use this static content block:
static content = {
myModule { $('myModule').module(CustomModule) }
namedModule { String name -> $(".$name").module(CustomModule) }
}
Both of the above items can be used successfully in my tests, but if I was to use 'namedModule' in one of my tests as follows:
page.namedModule("moduleName").moduleMethod("blah blah blah")
IntelliJ will highlight the parameter "moduleName" in yellow with the error:
'namedModule' cannot be applied to '(java.lang.String)'
We are trying to refactor our tests in a way that means you can navigate through the code easier (e.g. avoiding any Geb/Groovy 'magic' that IntelliJ can't resolve), and this is one of the last remaining issues preventing this from being possible.
This is a known limitation to Geb support in IntelliJ. IntelliJ always treats content definitions as properties of pages and modules even though they can be parametrised. Given that Geb support in IntelliJ is open sourced we could probably add support for this.
In the mean time, as a workaround you can use methods for parametrised content instead of content definitions and IntelliJ will be able to understand these and be able to refactor them:
void namedModule(String name) {
$(".$name").module(CustomModule)
}
There are some caveats, though:
you will loose ability to use content definition options; if you need to use these for a content definition then I suggest creating a parameterised "private" content definition (for example with a _ at the beginning of the name) that you will only ever access from within the page or module
RequiredPageContentNotPresent will not be thrown even if the returned content is empty; to work around it you will either need to add manual verification to each such method or use a strategy outlined in the first bullet point with using "private" content definitions

Kotlin synthetic in Adapter or ViewHolder

I am new in kotlin. I have found and tried to use synthetic method instead of annoying method findViewById in my Activity class, but I have found "If we want to call the synthetic properties on View (useful in adapter classes), we should also import kotlinx.android.synthetic.main.view.*." But I can't figure out how it exactly works? Is there any examples?
Simple example from https://github.com/antoniolg/Kotlin-for-Android-Developers
import kotlinx.android.synthetic.item_forecast.view.*
class ForecastListAdapter() : RecyclerView.Adapter<ForecastListAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindForecast(forecast: Forecast) {
itemView.date.text = forecast.date.toDateString()
}
}
}
No need to write
val view = itemView.findViewById(R.id.date) as TextView
view.text = forecast.date.toDateString()
Just
itemView.date.text = forecast.date.toDateString()
Simple and effective!
Kotling 1.1.4 out
Further information : https://antonioleiva.com/kotlin-android-extensions/
You need to enable Kotlin Android Extentions by adding this to your build.gradle:
apply plugin: 'org.jetbrains.kotlin.android.extensions'
androidExtensions {
experimental = true
}
Since this new version of Kotlin, the Android Extensions have incorporated some new interesting features: caches in any class (which interestingly includes ViewHolder)
Using it on a ViewHolder (or any custom class). Note that this class should implement LayoutContainer interface:
class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer {
fun bind(title: String) {
itemTitle.text = "Hello Kotlin!"
}
}
You need
import kotlinx.android.synthetic.row_wall.view.*
And later something along the lines of:
convertView.titleText.text = item.title
The point is that the view.* introduces extensions to the View class.
Try
class CustomViewModel(val baseView: View) {
val firstName = baseView.firstName
val lastName = baseView.lastName
}
View object exposes the views
ref:https://discuss.kotlinlang.org/t/unable-to-use-kotlin-android-extension-in-adapter-class/2890
If you are using the latest version l;.you don't have to add experimental = true to it.
in Project level Gradle
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21'
And in app level Gradle
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' //These should be on the top of file.
and in dependencies..
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21'
and import below as
import kotlinx.android.synthetic.main.your_layout_file_name.view.*
and example
import kotlinx.android.synthetic.main.item_animal.view.*
class AnimalVH(parent: ViewGroup, layoutID: Int) : BaseViewHolder<Animal>(parent, layoutID) {
override fun bindData(animal: Animal) {
itemView.tv_animal.text = animal.title
}
}
where BaseViewHolder is
abstract class BaseViewHolder<T>(parent: ViewGroup, layoutID: Int) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(layoutID, parent, false)
) {
abstract fun bindData(model: T)
}
It means you have to place this line at the beginning of your source file:
import kotlinx.android.synthetic.main.view.*
So now instead of, for example, findView(R.id.textView) as TextView you would write just textView. The latter is a synthetic extension property located in the package kotlinx.android.synthetic.main.view, that's why you have to import everything from it.
There's a tutorial on the official site, take a look.
FYI: Data binding is recommended over synthetic for view lookups.
Comment from a DA for Android from Google on Reddit
Hey! Developer Advocate for Android at Google here!
I wanted to add a bit of background here. Kotlin Extensions with
synthetic views was never intentionally “recommended” though that
shouldn’t be taken as a recommendation to not use them. If they're
working for you please feel free to continue using them in your app!
We’ve been shifting away from them (e.g. we don’t teach them in the
Udacity course) because they expose a global namespace of ids that’s
unrelated to the layout that’s actually inflated with no checks
against invalid lookups, are Kotlin only, and don't expose nullability
when views are only present in some configuration. All together, these
issues cause the API to increase number of crashes for Android apps.
On the other hand, they do offer a lightweight API that can help
simplify view lookups. In this space it's also worth taking a look at
Data Binding which also does automatic view lookups - as well as
integrates with LiveData to automatically update your views as data
changes.
Today, there's a few options in this space that work:
Data Binding is the recommendation for view lookup as well as binding,
but it does add a bit of overhead when compared to Android Kotlin
Extensions. It's worth taking a look to see if this is a good fit for
your app. Data Binding also allows you to observe LiveData to bind
views automatically when data changes. Compared to Kotlin Extensions,
it adds compile time checking of view lookups and type safety. Android
Kotlin Extensions is not officially recommended (which is not the same
as recommendation against). It does come with the issues mentioned
above, so for our code we're not using them. Butter Knife is another
solution that is extremely popular and works for both Kotlin and the
Java Programming Language. Reading through the comments here there's a
lot of developers that are having great luck with Kotlin Extensions.
That's great - and something we'll keep in mind as we look at ways to
continue improving our APIs. If you haven't taken a look at Data
Binding, definitely give it a shot.
As an aside, our internal code style guide is not intended to be
directly applied outside of our codebase. For example, we use
mPrefixVariables, but there's no reason that every app should follow
that style.

Possibility to complete current statement with anonymous implementation

Is there a shortcut (or menu entry) in IntelliJ IDEA to complete the current statement with an anonymous implementation. To make this more understandable:
I type something like: view.setOnClickListener( and would now like a shortcut to get the completion to:
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
<<new caret position>>
}
});
Assuming that the setOnClickListener method would accept one parameter of type OnClickListener. I don't care if there are already implementations of OnClickListener I would just like the very basic type (or interface) that is expected as an anonymous inner class.
So do anyone know if there is a possibility for this in IntelliJ IDEA, without the need of typing new OnClickListener (with auto completion) yourself?
The only thing you have to do is to write:
view.setOnClickListener(new + Ctrl+Shift+Space
This will bring up a list of alternatives but since you used the Smart Type Code Completion (instead of Basic Code Completion which is Ctrl+Space) you will have the one that fits best at the top.
So now just press Tab and the rest will be filled in for you.
You save some writing but still have to at least write new in order for it to work.
Smart Type code completion filters the suggestion list and includes only those types that are applicable to the current context.

Why does adding 2nd level subclassed Button controls to a Grid give E_INVALIDARG?

I've come across the this problem dealing with subclasses of the Windows.UI.Xaml.Button class in C++/CX, and I'd like to know what's going on.
If I add a control instance to a grid, everything works as expected.
If I subclass the control and add an instance of the subclass, everything works as expected.
But if I subclass my subclassed control and add an instance of it to the grid I get E_INVALIDARG thrown during Grid::Children::Append(). What gives?
My code looks roughly like this (LayoutRoot is a Grid in MainPage.xaml, this sample has been tested in an empty simple metro application):
// Scenario 1: This works (duh!)
LayoutRoot->Children->Append(ref new Button());
// Scenario 2: This works
LayoutRoot->Children->Append(ref new MyButton1());
// Scenario 3: This doesn't work, it will cause an E_INVALIDARG to be thrown by the collection
LayoutRoot->Children->Append(ref new MyButton2());
// This is how MyButton1 and MyButton2 is defined
public ref class MyButton1 : public Button {
public:
MyButton1() {};
~MyButton1() {};
};
public ref class MyButton2 : public MyButton1 {
public:
MyButton2() {};
~MyButton2() {};
};
Note that this question is slightly similar to this question, but the error and the scenario is sufficiently different for me to post this one separately.
UPDATE: I think I'm on the right track understanding this problem after reading this article by Ian Griffiths, but I need to know more regarding the behavior of this specific example. Full code to repeat this problem can be found here, see the 3rd post in the thread.
UPDATE: From what I've learned so far, not all WinRT types support inheritance. I have no reliable source references for this, but I've read that the Windows.UI.Xaml classes should support inheritance, but other WinRT types won't. The Windows.UI.Xaml.Controls.Button class obviously does, while my own MyButton1 doesn't. I'd like to know what I'd have to do to make MyButton1 'inheritable' the way the Button class is.
I've found that replacing the Windows.UI.Xaml.Controls.Button class with Windows.UI.Xaml.Controls.ProgressBar will make scenario 2 fail, which tells me that the ProgressBar class isn't (yet) possible to subclass. This observation is what makes me believe that a class need to do something explicit in order for it to be inheritable.

Weird JavaCore IType cache problem

I'm developing a plugin that takes all enums in workspace that implements certain interface (IDomain) parses the code (Using AST) does some modification over the enum and marks it as processed with an annotation (#IDomainInfo).
For example, it takes someting like this:
public
enum SomeEnum implements IDomain {
// ...
}
And generates something like this:
public #IDomainInfo(domainId = 1)
enum SomeEnum implements IDomain {
// Some changes here...
}
The idea behind of the #IDomainInfo is that annotated enums have not to be processed anymore by the plugin.
Basically what I do to accomplish the task is to make a search with JavaSearch API to find all the enums implementing IDomain (easy task), and as result I get a list of IJavaElements (which are in fact instances of IType). Then I call a method that iterates through the resulting list and creates a new list of all the IType instances that are not annotated with #IDomainInfo and then process the resulting list: For each non annotated IType do some work, annotate the IType with the #IDomainInfo annotation (Using AST) and then save back the results to file (using IFile, so I can see the changes without refresh, and in fact, if I have the enum open in the editor I see it refreshed instantly :-)
All that works fine, but if I open an #IDomainInfo annotated enum (just for testing) then remove the #IDomainInfo, save the file (I'm sure) and then call the action that does all the job I've described before, when I get to the part that filters annotated IType from non annotated ones, code is something like this:
for (IType type : typeList) {
IAnnotation annotation = type.getAnnotation(“IDomainInfo”);
if (!annotation.exists()) {
// The annotation does not exist, so add the type to the
// list of elements to update and go on...
ret.add(type);
continue;
}
// Something else here...
}
Well, it results that for the file I've just saved the IType detects the annotation I've just removed as if it's still there. If I close and reopen eclipse all works normally.
Now, I've just checked and triple checked my code, so I'm sure that I'm not keeping a stale copy of the old IType unedited still with the annotation version (all my IType come from a fresh java search call every time I run the action).
So the question is, what might I be doing wrong? I mean, I've just read the JavaCore API many times to check If I might be using it wrong or if I have some conceptual flaw there but really I have no clue, it's like if eclipse would be caching the IType ignoring the changes I've just made in the editor :-/
If any one have an idea I would appreciate it a lot :-)
When or how is your plugin called ? Did you register a resource listener or is it a project builder or something else ? If it is called by a resource listener, your plugin may be reading the 'primary copy' for your IType, which has not been saved yet. Hence your changes are still in the Working Copy.