feat(templates): add helper methods to create values resources

This commit is contained in:
Akash Yadav
2023-05-13 18:27:32 +05:30
parent e3031c527c
commit 80bd4ce3ee
12 changed files with 506 additions and 112 deletions
@@ -15,9 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.xml.utils;
/**
/*
* Copyright (c) 2018 Angelo ZERR All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0 which accompanies this
* distribution, and is available at http://www.eclipse.org/legal/epl-v20.html
@@ -27,6 +25,8 @@ package com.itsaky.androidide.lsp.xml.utils;
* <p>Contributors: Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
*/
package com.itsaky.androidide.lsp.xml.utils;
import static org.eclipse.lemminx.utils.StringUtils.normalizeSpace;
import com.itsaky.androidide.lsp.xml.models.XMLServerSettings;
@@ -0,0 +1,211 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.itsaky.androidide.xml.utils;
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.PreferenceTags.PREFERENCE_CATEGORY;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
/**
* Builds XML strings. Arguments are not validated or escaped. This class is designed to replace
* hand writing XML snippets in string literals.
*/
public final class XmlBuilder {
private enum Construct {
NULL,
START_TAG,
ATTRIBUTE,
CHARACTER_DATA,
END_TAG
}
private final StringBuilder stringBuilder = new StringBuilder();
private Construct lastAppendedConstruct = Construct.NULL;
private int indentationLevel;
@NonNull
public XmlBuilder startTag(@NonNull String name) {
if (!lastAppendedConstruct.equals(Construct.END_TAG)) {
int length = stringBuilder.length();
if (length > 0) {
stringBuilder.replace(length - 1, length, ">\n");
}
}
if (indentationLevel != 0) {
stringBuilder.append('\n');
}
indent();
stringBuilder.append('<').append(name).append('\n');
indentationLevel++;
lastAppendedConstruct = Construct.START_TAG;
return this;
}
@NonNull
public XmlBuilder androidAttribute(@NonNull String name, boolean value) {
return androidAttribute(name, Boolean.toString(value));
}
@NonNull
public XmlBuilder androidAttribute(@NonNull String name, int value) {
return androidAttribute(name, Integer.toString(value));
}
@NonNull
public XmlBuilder androidAttribute(@NonNull String name, @NonNull String value) {
return attribute(SdkConstants.ANDROID_NS_NAME, name, value);
}
@NonNull
public XmlBuilder attribute(@NonNull String name, @NonNull String value) {
return attribute("", name, value);
}
@NonNull
public XmlBuilder attribute(
@NonNull String namespacePrefix, @NonNull String name, @NonNull String value) {
indent();
if (!namespacePrefix.isEmpty()) {
stringBuilder.append(namespacePrefix).append(':');
}
stringBuilder.append(name).append("=\"").append(value).append("\"\n");
lastAppendedConstruct = Construct.ATTRIBUTE;
return this;
}
@NonNull
public XmlBuilder wrapContent() {
return withSize(VALUE_WRAP_CONTENT, VALUE_WRAP_CONTENT);
}
@NonNull
public XmlBuilder matchParent() {
return withSize(VALUE_MATCH_PARENT, VALUE_MATCH_PARENT);
}
@NonNull
public XmlBuilder withSize(@NonNull String width, @NonNull String height) {
androidAttribute(ATTR_LAYOUT_WIDTH, width);
androidAttribute(ATTR_LAYOUT_HEIGHT, height);
return this;
}
@NonNull
public XmlBuilder characterData(@NonNull String data) {
if (lastAppendedConstruct.equals(Construct.START_TAG)
|| lastAppendedConstruct.equals(Construct.ATTRIBUTE)) {
int length = stringBuilder.length();
stringBuilder.replace(length - 1, length, ">\n");
}
indent();
stringBuilder.append(data).append('\n');
lastAppendedConstruct = Construct.CHARACTER_DATA;
return this;
}
@NonNull
public XmlBuilder appendDirectly(String content) {
stringBuilder.append(content);
return this;
}
@NonNull
public XmlBuilder endTag(@NonNull String name) {
return endTagImpl(name, !name.endsWith("Layout") && !name.equals(PREFERENCE_CATEGORY));
}
@NonNull
public XmlBuilder seperateEndTag(@NonNull String name) {
return endTagImpl(name, false);
}
@NonNull
private XmlBuilder endTagImpl(@NonNull String name, boolean useEmptyElementTag) {
if (lastAppendedConstruct.equals(Construct.START_TAG)
|| lastAppendedConstruct.equals(Construct.ATTRIBUTE)) {
int length = stringBuilder.length();
if (useEmptyElementTag) {
stringBuilder.deleteCharAt(length - 1);
} else {
stringBuilder.replace(length - 1, length, ">\n\n");
}
}
indentationLevel--;
if ((lastAppendedConstruct.equals(Construct.START_TAG)
|| lastAppendedConstruct.equals(Construct.ATTRIBUTE))
&& useEmptyElementTag) {
stringBuilder.append(" />\n");
} else {
indent();
stringBuilder.append("</").append(name).append(">\n");
}
lastAppendedConstruct = Construct.END_TAG;
return this;
}
private void indent() {
for (int i = 0; i < indentationLevel; i++) {
stringBuilder.append(" ");
}
}
@NonNull
@Override
public String toString() {
return stringBuilder.toString();
}
}
@@ -0,0 +1,34 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.utils
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
/**
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
class XmlBuilderTest {
@Test
fun `test values res build`() {
}
}
@@ -30,8 +30,8 @@ import java.io.File
class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
val manifestBuilder = AndroidManifestBuilder()
val resManager = AndroidModuleResManager()
val manifest = AndroidManifestBuilder()
val res = AndroidModuleResManager()
/**
* Return the file path to `AndroidManifest.xml`.
@@ -45,7 +45,7 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
* Configure the properties for `AndroidManifest.xml` file.
*/
fun manifest(block: AndroidManifestBuilder.() -> Unit) {
manifestBuilder.apply(block)
manifest.apply(block)
}
/**
@@ -72,7 +72,7 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
* @param configure Function to configure the resources.
*/
fun RecipeExecutor.res(configure: AndroidModuleResManager.() -> Unit) {
resManager.apply(configure)
res.apply(configure)
}
/**
@@ -100,7 +100,7 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
}
override fun RecipeExecutor.preConfig() {
manifestBuilder.apply {
manifest.apply {
packageName = data.packageName
isLibrary = data.type == AndroidLibrary
}
@@ -110,7 +110,7 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
}
override fun RecipeExecutor.postConfig() {
manifestBuilder.apply {
manifest.apply {
generate(manifestFile())
}
}
@@ -29,11 +29,13 @@ import com.itsaky.androidide.templates.base.baseAndroidXDependencies
* @param copyDefAssets Whether to copy the default Android assets (except `values` directory) to this module.
* @param block The module configurator.
*/
fun ProjectTemplateBuilder.defaultAppModule(name: String = ":app", addAndroidX: Boolean = true,
fun ProjectTemplateBuilder.defaultAppModule(name: String = ":app",
addAndroidX: Boolean = true,
copyDefAssets: Boolean = true,
block: AndroidModuleTemplateConfigurator
) {
check(defModuleTemplate == null) { "Default module has been already configured" }
check(
defModuleTemplate == null) { "Default module has been already configured" }
val module = AndroidModuleTemplateBuilder().apply {
_name = name
@@ -31,8 +31,8 @@ import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.base.modules.android.ManifestActivity
import com.itsaky.androidide.templates.base.modules.android.ManifestIcon
import com.itsaky.androidide.xml.permissions.Permission
import com.itsaky.androidide.xml.utils.XmlBuilder
import java.io.File
import com.android.xml.XmlBuilder as XMLBuilder
/**
* Builder for building `AndroidManifest.xml` file for an Android module.
@@ -102,12 +102,12 @@ class AndroidManifestBuilder {
}
private fun manifestSrc(): String {
return XMLBuilder().apply {
return XmlBuilder().apply {
buildManifest()
}.withXmlDecl()
}
private fun XMLBuilder.buildManifest() {
private fun XmlBuilder.buildManifest() {
createElement(TAG_MANIFEST) {
attr(name = ANDROID_NS_NAME, value = ANDROID_URI, ns = XMLNS)
@@ -116,7 +116,7 @@ class AndroidManifestBuilder {
}
}
private fun XMLBuilder.permissions() {
private fun XmlBuilder.permissions() {
for (permission in permissions) {
createElement(TAG_USES_PERMISSION) {
androidAttr("name", permission.constant)
@@ -124,7 +124,7 @@ class AndroidManifestBuilder {
}
}
private fun XMLBuilder.application() {
private fun XmlBuilder.application() {
if (isLibrary) {
return
}
@@ -141,7 +141,7 @@ class AndroidManifestBuilder {
}
}
private fun XMLBuilder.activities() {
private fun XmlBuilder.activities() {
for (activity in activities) {
createElement(TAG_ACTIVITY) {
androidAttr("name", activity.name(packageName))
@@ -155,7 +155,7 @@ class AndroidManifestBuilder {
}
}
private fun XMLBuilder.intentFilter() {
private fun XmlBuilder.intentFilter() {
createElement(TAG_INTENT_FILTER) {
// action
createElement(TAG_ACTION) {
@@ -169,11 +169,11 @@ class AndroidManifestBuilder {
}
}
private fun XMLBuilder.androidAttr(name: String, value: String) {
private fun XmlBuilder.androidAttr(name: String, value: String) {
androidAttribute(name, value)
}
private fun XMLBuilder.attr(name: String, value: String, ns: String = "") {
private fun XmlBuilder.attr(name: String, value: String, ns: String = "") {
attribute(ns, name, value)
}
@@ -18,8 +18,9 @@
package com.itsaky.androidide.templates.base.util
import com.android.aaptcompiler.ConfigDescription
import com.android.xml.XmlBuilder
import com.itsaky.androidide.xml.utils.XmlBuilder
import com.itsaky.androidide.templates.SrcSet
import com.itsaky.androidide.templates.SrcSet.Main
import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder
import java.io.File
@@ -55,12 +56,33 @@ class AndroidModuleResManager {
var name = type.dirName
config.toString().also {
if (it != "DEFAULT") {
name += it
name += "-${it}"
}
}
return File(resDir(srcSet), name).also { it.mkdirs() }
}
/**
* Create a new XML values resource file for the given [resource type][type] and
* the [configuration][config]. The `<resources>` tag is already appended
* to the [XmlBuilder].
*
* @param name The name of the resource without the `.xml` extension.
* @param type The resource type.
* @param srcSet The source set.
* @param config The configuration for the resource type.
*/
fun AndroidModuleTemplateBuilder.createValuesResource(name: String,
type: ResourceType,
srcSet: SrcSet = Main,
config: ConfigDescription = ConfigDescription(),
configure: XmlBuilder.() -> Unit
) {
return createXmlResource(name, type, srcSet, config) {
createElement("resources", configure)
}
}
/**
* Create a new XML resource file for the given [resource type][ResourceType] and the [configuration][ConfigDescription].
*
@@ -17,7 +17,7 @@
package com.itsaky.androidide.templates.base.util
import com.android.xml.XmlBuilder
import com.itsaky.androidide.xml.utils.XmlBuilder
/**
* Creates a new element in the [XmlBuilder] with the given [name].
@@ -30,9 +30,29 @@ fun XmlBuilder.createElement(name: String, configure: XmlBuilder.() -> Unit) {
endTag(name)
}
/**
* Adds a new `<string>` resource entry to this [XmlBuilder].
*
* @param name The name of the string resource.
* @param value The value of the XML resource.
*/
fun XmlBuilder.addStringRes(name: String, value: String) {
createElement("string") {
attribute("name", name)
text(value)
}
}
/**
* Get the generated XML string from the [XmlBuilder] with XML declaration prepended.
*/
fun XmlBuilder.withXmlDecl() : String {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n${toString()}"
fun XmlBuilder.withXmlDecl(): String {
return toString().withXmlDecl()
}
/**
* Prepends XML declaration to this string.
*/
fun String.withXmlDecl(): String {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n${this}"
}
@@ -0,0 +1,26 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.impl.base
import com.itsaky.androidide.templates.base.util.withXmlDecl
internal fun emptyValuesFile(): String {
return """
<resources></resources>
""".trim().withXmlDecl()
}
@@ -0,0 +1,32 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.impl.base
internal fun simpleMaterial3Theme(themeName: String) : String {
return """
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.${themeName}" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="$themeName" parent="Base.${themeName}" />
</resources>
""".trim()
}
@@ -17,14 +17,20 @@
package com.itsaky.androidide.templates.impl.emptyActivity
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.android.ResTableConfig
import com.itsaky.androidide.templates.Language
import com.itsaky.androidide.templates.ProjectTemplate
import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder
import com.itsaky.androidide.templates.base.baseProject
import com.itsaky.androidide.templates.base.modules.android.ManifestActivity
import com.itsaky.androidide.templates.base.modules.android.defaultAppModule
import com.itsaky.androidide.templates.base.util.AndroidModuleResManager
import com.itsaky.androidide.templates.base.util.AndroidModuleResManager.ResourceType.LAYOUT
import com.itsaky.androidide.templates.base.util.AndroidModuleResManager.ResourceType.VALUES
import com.itsaky.androidide.templates.base.util.SourceWriter
import com.itsaky.androidide.templates.impl.base.emptyValuesFile
import com.itsaky.androidide.templates.impl.base.simpleMaterial3Theme
fun emptyActivityProject(): ProjectTemplate = baseProject {
defaultAppModule {
@@ -44,103 +50,41 @@ internal fun AndroidModuleTemplateBuilder.writeEmptyActivity(
resManager: AndroidModuleResManager
) {
resManager.apply {
writeXmlResource("activity_main", LAYOUT) {
"""
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello user!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
""".trim()
val configNight = ConfigDescription().apply {
uiMode = ResTableConfig.UI_MODE.NIGHT_YES
}
// layout/activity_main.xml
writeXmlResource("activity_main", LAYOUT, source = ::emptyLayoutSrc)
// values
writeXmlResource("themes", VALUES,
source = simpleMaterial3Theme(manifest.themeRes))
writeXmlResource("colors", VALUES, source = emptyValuesFile())
// values-night
writeXmlResource("themes", VALUES, config = configNight,
source = simpleMaterial3Theme(manifest.themeRes))
writeXmlResource("colors", VALUES, config = configNight,
source = emptyValuesFile())
}
}
internal fun AndroidModuleTemplateBuilder.writeEmptyActivity(
writer: SourceWriter
) {
val className = "MainActivity"
writer.apply {
if (data.language == Language.Kotlin) {
writeKtSrc(data.packageName, "MainActivity",
source = this@writeEmptyActivity::emptyActivityKtSrc)
writeKtSrc(data.packageName, className,
source = ::emptyActivityKtSrc)
} else {
writeJavaSrc(packageName = data.packageName, className = "MainActivity",
source = this@writeEmptyActivity::emptyActivityJavaSrc)
writeJavaSrc(packageName = data.packageName, className = className,
source = ::emptyActivityJavaSrc)
}
}
}
private fun AndroidModuleTemplateBuilder.emptyActivityKtSrc(): String {
return """
package ${data.packageName}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import ${data.packageName}.databinding.ActivityMainBinding
public class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
private val binding: ActivityMainBinding
get() = checkNotNull(_binding) { "Activity has been destroyed" }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate and get instance of binding
_binding = ActivityMainBinding.inflate(layoutInflater)
// set content view to binding's root
setContentView(binding.root)
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
"""
}
private fun AndroidModuleTemplateBuilder.emptyActivityJavaSrc(): String {
return """
package ${data.packageName};
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import ${data.packageName}.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate and get instance of binding
binding = ActivityMainBinding.inflate(getLayoutInflater());
// set content view to binding's root
setContentView(binding.getRoot());
}
@Override
protected void onDestroy() {
super.onDestroy();
this.binding = null;
}
}
"""
}
manifest {
addActivity(ManifestActivity(name = className, isExported = true, isLauncher = true))
}
}
@@ -0,0 +1,103 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.impl.emptyActivity
import com.itsaky.androidide.templates.base.AndroidModuleTemplateBuilder
internal fun emptyLayoutSrc() = """
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello user!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
""".trim()
internal fun AndroidModuleTemplateBuilder.emptyActivityKtSrc(): String {
return """
package ${data.packageName}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import ${data.packageName}.databinding.ActivityMainBinding
public class MainActivity : AppCompatActivity() {
private var _binding: ActivityMainBinding? = null
private val binding: ActivityMainBinding
get() = checkNotNull(_binding) { "Activity has been destroyed" }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate and get instance of binding
_binding = ActivityMainBinding.inflate(layoutInflater)
// set content view to binding's root
setContentView(binding.root)
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
"""
}
internal fun AndroidModuleTemplateBuilder.emptyActivityJavaSrc(): String {
return """
package ${data.packageName};
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import ${data.packageName}.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate and get instance of binding
binding = ActivityMainBinding.inflate(getLayoutInflater());
// set content view to binding's root
setContentView(binding.getRoot());
}
@Override
protected void onDestroy() {
super.onDestroy();
this.binding = null;
}
}
"""
}