pocmo / recompose Goto Github PK
View Code? Open in Web Editor NEWrecompose is a tool for converting Android layouts in XML to Kotlin code using Jetpack Compose.
License: Apache License 2.0
recompose is a tool for converting Android layouts in XML to Kotlin code using Jetpack Compose.
License: Apache License 2.0
ParameterValue.ColoValue
should be Parameter.ColorValue
. :)
Currently the plugin only reacts to, and processes, XML code that was copied inside the IDE and then pasted somewhere. We should also support the translation of XML code that was copied from outside of the IDE, e.g. from a website:
https://developer.android.com/guide/topics/ui/declaring-layout#write
Okay, the CLI is not implemented yet. Let's do that. :)
Will be needed for ConstraintLayout
(#12).
I saw you were looking for a higher level for KotlinWriter?
Maybe something like this is an option?
The code below
internal class KotlinWriterHelper(
val writer: KotlinWriter
) {
operator fun String.invoke(
vararg parameters: CallParameter?,
block: (KotlinWriter.() -> Unit)? = null
) = writer.writeCall(
name = this,
parameters = parameters.toList(),
block = block
)
fun write(writer: KotlinWriterHelper.() -> Unit) = writer()
}
would change this code
writer.writeCall(
"Row",
parameters = listOf(
rowModifier.toCallParameter()
)
) {
writeCall(
name = "RadioButton",
parameters = listOf(
CallParameter(
name = "selected",
value = ParameterValue.RawValue(node.checked)
),
CallParameter(
name = "onCheckedChange",
value = ParameterValue.EmptyLambdaValue
)
)
)
}
into
write{
"Row"(
rowModifier.toCallParameter()
){
"RadioButton"(
CallParameter(
name = "selected",
value = ParameterValue.RawValue(node.checked)
),
CallParameter(
name = "onCheckedChange",
value = ParameterValue.EmptyLambdaValue
)
)
}
Adding functions like:
internal infix fun String.withSizeValue(size: Size) = CallParameter(ParameterValue.SizeValue(size), this)
internal infix fun String.withColorValue(color: Color) = CallParameter(ParameterValue.ColorValue(color), this)
internal infix fun String?.withRawValue(raw: String) = CallParameter(ParameterValue.RawValue(raw), this)
internal infix fun String.withRawValue(raw: Boolean) = CallParameter(ParameterValue.RawValue(raw), this)
internal infix fun String.withRawValue(raw: Int) = CallParameter(ParameterValue.RawValue(raw), this)
internal infix fun String?.withStringValue(raw: String) = CallParameter(ParameterValue.StringValue(raw), this)
internal fun String.withEmptyLambda() = CallParameter(ParameterValue.EmptyLambdaValue, this)
internal infix fun String.withKeyboardType(inputType: InputType) = CallParameter(ParameterValue.KeyboardTypeValue(inputType), this)
would simplify it to:
write{
"Row"(
rowModifier.toCallParameter()
){
"RadioButton"(
"selected" withRawValue node.checked,
"onCheckedChange".withEmptyLambda()
)
}
(these params could be written in an interal interface, which then would be implemented by the KotlinWriterHelper)
We then could make the names values or getters inside an interface:
val Text = "Text"
val Box = "Box"
val Button = "Button"
val Row = "Row"
val Checkbox = "Checkbox"
val RadioButton = "RadioButton"
val Card = "Card"
val Image = "Image"
val TextField = "TextField"
Such that we can write:
write{
Row(
rowModifier.toCallParameter()
){
RadioButton(
"selected" withRawValue node.checked,
"onCheckedChange".withEmptyLambda()
)
}
And we could add a Text function:
fun KotlinWriterHelper.Text(
vararg params : CallParameter?,
text: String? = null,
) = "Text"(
text?.let {
null withStringValue it
},
*params
)
)
(I placed the varargs at the start, as that would keep compiling all the old code)
Not sure what a good Composable
for that is? ๐ค
Currently we do not use line breaks for formatting.
So for example we'd create the following Text
Composable:
Text(text = "I am a test", color = Color(0xffffcc00.toInt()), fontSize = 20.sp, modifier = Modifier.width(100.dp).background(Color(0xaa0000ff.toInt())))
We probably should define a threshold of number of arguments/modifier, where we start using line breaks, e.g.:
Text(
text = "I am a test",
color = Color(0xffffcc00.toInt()),
fontSize = 20.sp,
modifier = Modifier
.width(100.dp)
.background(Color(0xaa0000ff.toInt()))
)
alpha04 was just released and as expected some things have changed. Since that is likely to happen more often, it would be nice if we could compile the generated code for our test cases against the jetpack compose libraries. That would reveal breakage whenever we switch to the latest version.
#11 added basic support for FrameLayout
. This issue is about adding support for gravity
so that children can be positioned in the FrameLayout
/ Box
.
Also see discussion about how to map between the different gravity representations here: #11 (comment) about
https://developer.android.com/reference/android/widget/EditText
<EditText
android:id="@+id/plain_text_input"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:inputType="text"/>
View:
https://developer.android.com/reference/android/widget/FrameLayout
Composable:
Maybe we can just translate it to a Box()
with children?
XML:
https://developer.android.com/reference/android/widget/ImageButton
Compose:
I assume it will be something like:
Button(..) {
Image(..)
}
Currently when pasting with the plugin we only add the code. But we can also add the required imports to the list of import statements (e.g. import androidx.compose.foundation.Text
).
From reading the code of the Kotlin language plugin, I was expecting something like this to work:
val import = KtPsiFactory(project, true).createImportDirective(
ImportPath(FqName("androidx.compose.foundation.Text"), false)
)
val target = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) as KtFile
target.importList!!.imports.add(import)
But I end up with the following error:
e: recompose/recompose-idea/src/main/kotlin/recompose/plugin/copypaste/RecomposeCopyPasteProcessor.kt: (101, 16): Cannot access 'com.intellij.psi.PsiClassOwner' which is a supertype of 'org.jetbrains.kotlin.psi.KtFile'. Check your module classpath for missing or conflicting dependencies
e: recompose/recompose-idea/src/main/kotlin/recompose/plugin/copypaste/RecomposeCopyPasteProcessor.kt: (101, 16): Cannot access 'com.intellij.psi.PsiModifiableCodeBlock' which is a supertype of 'org.jetbrains.kotlin.psi.KtFile'. Check your module classpath for missing or conflicting dependencies
I am not sure how to get access to those super types?
Currently we only support absolute colors like #ff0000
. But colors can also be references like @color/SomeColor
.
Right now we ignore all LayoutSize
except LayoutSize.Dp
. We should also rewrite match_parent
as Modifier.fillMaxWidth()
or Modifier.fillMaxHeight()
.
Hi, tnx for plugin,
is it possible to fix this error:
recompose.parser.Parser$ParserException: Unknown size value: @dimen/common_margin
at recompose.parser.values.SizeKt.size(Size.kt:31)
at recompose.parser.values.PaddingKt.padding(Padding.kt:29)
at recompose.parser.xml.ViewKt.viewAttributes(View.kt:82)
at recompose.parser.xml.viewgroup.LinearLayoutKt.linearLayout(LinearLayout.kt:33)
at recompose.parser.xml.ViewKt.node(View.kt:51)
at recompose.parser.xml.ViewGroupKt.viewGroupAttributes(ViewGroup.kt:37)
at recompose.parser.xml.viewgroup.UnknownKt.unknown(Unknown.kt:31)
at recompose.parser.xml.ViewKt.node(View.kt:69)
at recompose.parser.xml.ViewGroupKt.viewGroupAttributes(ViewGroup.kt:37)
at recompose.parser.xml.viewgroup.ConstraintLayoutKt.constraintLayout(ConstraintLayout.kt:32)
at recompose.parser.xml.ViewKt.node(View.kt:65)
at recompose.parser.xml.ViewGroupKt.viewGroupAttributes(ViewGroup.kt:37)
at recompose.parser.xml.viewgroup.UnknownKt.unknown(Unknown.kt:31)
at recompose.parser.xml.ViewKt.node(View.kt:69)
at recompose.parser.xml.LayoutKt.layout(Layout.kt:37)
at recompose.parser.Parser.parse(Parser.kt:62)
at recompose.parser.Parser.parse(Parser.kt:53)
at recompose.parser.Parser.parse(Parser.kt:37)
https://developer.android.com/reference/android/widget/ImageView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/my_image"
android:contentDescription="@string/my_image_description"
/>
</LinearLayout>
Composable example:
ConstraintLayout {
createHorizontalChain(eye_left, eye_right, chainStyle = ChainStyle.Packed)
...
}
Add support for these attributes: android:hint
, android:textColorHint
In the plugin code we are replacing the selection with the transformed Kotlin code here:
This yields a warning. IntelliJ wants us to wrap this in runWriteAction()
.
https://jetbrains.org/intellij/sdk/docs/basics/architectural_overview/general_threading_rules.html
Currently a drawable
value in ImageView
will always be written as:
Image(imageResource(R.id.the_drawable), ...)
This works for drawable resources that are PNGs. But it fails if the drawable is a vector image. In that case it needs to be:
Image(vectorResource(R.id.the_drawable), ...)
From the XML alone we cannot know if the drawable is a vector resource or not. We could try finding the drawable (assuming default folders are used), but this fails if the resource is not in the app itself (e.g. provided by a library or a different module).
Alternatively the plugin in the IDE could try to figure out what kind of drawable is used - but this will only work in the plugin and not in the CI.
In general I am surprised to see Jetpack Compose force making this distinction unto the consumer. Wouldn't it be more convenient to provide a drawable resource to Compose and it can figure out itself what it is?
CardView:
https://developer.android.com/reference/androidx/cardview/widget/CardView
We can translate this to Card()
.
A first implementation could just translate to Card
(with viewAttributes
applied). Later we can add more attributes and translate them (e.g. cardCornerRadius
to shape
).
com.google.android.material.card.MaterialCardView
extends androidx.cardview.widget.CardView
and could use the same parser.
View available in:
com.google.android.material:material:1.2.1
XML:
https://developer.android.com/reference/android/widget/RelativeLayout
I am not sure what a good Composable
replacement is?
XML:
https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView
I'm not sure what a good Composable
replacement for it is and whether the conversion will be straight-forward?
fun Modifier.padding(
start: Dp = 0.dp,
top: Dp = 0.dp,
end: Dp = 0.dp,
bottom: Dp = 0.dp
)
The parser seems to close a parent tag for EditText.
FrameLayout {
EditText()
View()
}
Will result in:
FrameLayout {
EditText()
}
View()
Same occurs with LinearLayout.
XML:
https://developer.android.com/reference/android/widget/RadioButton
Composable:
?
The usual. Run build and tests on PRs and pushes.
Currently we only recognize wrap_content
and match_parent
. We should also support absolute sizes like 250dp
.
Currently the Composer
(code) is an independent class that writes Kotlin code and outputs it as a string. This string is then used by the plugin and CLI.
What if we'd implement a Composer
that knows its inside an IntelliJ / Android Studio plugin and can make use of the environment and tools to write the code to the destination. A big advantage would be that we'd get access to the AST of the surrounding code and could, for example, figure out if a drawable is an image or a vector (#69). Maybe we would also be able to use the internals for generating/writing code and wouldn't need to write a plain string?
Considering the composer is the "backend" and the parser is the "frontend" we may also be able to replace the "frontend" and let IntelliJ parse the XML and give us access to its AST?
A downside of this would be that this would basically reduce this project to the plugin and potentially kill the CLI. But then again, maybe parts of it could also survive in a standalone version. After all projects like detekt also make use of the parts of the Kotlin compiler without being a plugin.
Having a closer look at the Java to Kotlin conversion in the Kotlin plugin may give some hints about what is possible.
android:maxLines
Makes the TextView be at most this many lines tall. When used on an editable text, the inputType attribute's value must be combined with the textMultiLine flag for the maxLines attribute to apply.
XML attribute:
https://developer.android.com/reference/android/widget/TextView?hl=en#attr_android:maxLines
Jetpack Compose:
Text(
...
maxLines = Int = Int.MAX_VALUE
...
)
We are parsing TextView
here:
https://github.com/pocmo/recompose/blob/main/recompose-parser/src/main/kotlin/recompose/parser/xml/view/TextView.kt
And create a TextViewNode
from it:
https://github.com/pocmo/recompose/blob/main/recompose-ast/src/main/kotlin/recompose/ast/view/TextViewNode.kt
And we turn the node into Kotlin code here:
https://github.com/pocmo/recompose/blob/main/recompose-composer/src/main/kotlin/recompose/composer/visitor/ComposingVisitor.kt#L82-L94
To solve this issue:
TextViewNode
. Probably a nullable Int?
is needed.ComposingVisitor
.ParserTest
and ComposerTest
.Currently we just bail if we hit an XML element that we do not recognize:
https://github.com/pocmo/recompose/blob/main/recompose-parser/src/main/kotlin/recompose/parser/xml/View.kt#L48
This is of course pretty annoying for someone using the plugin. Ideally we'd parse it as an UnknownNode
and transform this in the Composer
into a comment that will make the user aware that there's a piece missing here. With that the user still gets the benefit of translating everything else and can then fill in the blanks.
The plugin cannot handle a layout with a style attribute (like ?selectableItemBackground
)
Using ConstraintLayout
you'd often set android:layout_width
or android:layout_height
of a View
to 0dp
to let the ConstraintLayout stretch it based on the constraints. ConstraintLayout()
doesn't behave that way if you add a width(0.dp)
modifier. In that case we need to translate it to fillMaxWidth()
or fillMaxHeight()
.
For now we could just always translate 0dp
to fillMaxWidth()
or fillMaxHeight()
if the node has any constraint set.
Currently when copying layout XML with the plugin and pasting it into code, we will translate the whole file and paste the whole translation. That's of course not right and we need to look at the actual selection using startOffsets
and endOffsets
.
Of course using the selection means we may get incomplete XML or multiple "root" nodes. I am not sure what XMLPullParser
will do with that. We may need to "fix up" the XML before processing it. Or manually reduce the selection to a valid XML subset.
Needed for #5.
The XMLPullParser
implementation only likes well-formed XML documents. E.g. when having multiple root nodes:
start tag not allowed in epilog but got T (position: END_TAG seen ... android:text="padding"\n android:background="#ff0000" />\n\n<T... @9:3)
org.xmlpull.v1.XmlPullParserException: start tag not allowed in epilog but got T (position: END_TAG seen ... android:text="padding"\n android:background="#ff0000" />\n\n<T... @9:3)
But we also want to be able to recompose partial XML documents (e.g. see #5).
We could:
XMLPullParser
supports a quirks mode that will continue parsing documents that are not strictly following the XML standard.XML:
https://developer.android.com/reference/android/widget/Switch
Composable:
?
When pasting translated Kotlin code with the plugin we should check whether the destination is valid (Is it a Kotlin file?).
Right now if you copy XML and paste it back into an XML then we paste Kotlin code back into the XML file. :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.