Неразрешенная ссылка для синтетического представления, когда макет находится в библиотечном модуле

с использованием Kotlin 1.20.20 (не то чтобы это важно, старые версии ведут себя так же)

Когда макет находится в отдельном библиотечном модуле, у Android Studio нет проблем с поиском и ссылкой на представление

import kotlinx.android.synthetic.main.view_user_input.*

Но когда я пытаюсь скомпилировать приложение, оно терпит неудачу с Unresolved reference: view_user_input на :app:compileDebugKotlin.

Все работает нормально, когда в библиотечном модуле есть ссылка на представление.

Я что-то упустил?

Добавление структуры проекта. Все модули используют kotlin и kotlin-расширения.

build.gradle
app/
  build.gradle //main application
library-module-a/
  build.gradle //library application
library-module-b/
  build.gradle //library application

Вот пример приложения https://github.com/mjurkus/KotlinxTest.

Зарегистрированная проблема для этого в трекере KT


person Martynas Jurkus    schedule 22.01.2018    source источник
comment
где вы определяете kotlin buildScript? в проекте или модуле?   -  person Hemant Parmar    schedule 22.01.2018
comment
В проекте. Если вы спрашиваете, где определен `classpath org.jetbrains.kotlin: kotlin-android-extensions: $ KOTLIN_VERSION`.   -  person Martynas Jurkus    schedule 22.01.2018
comment
просто удалили из проекта и добавили в модуль.   -  person Hemant Parmar    schedule 22.01.2018
comment
Почему? Не могли бы вы рассказать подробнее?   -  person Martynas Jurkus    schedule 22.01.2018
comment
я уже сталкиваюсь с этой проблемой, чтобы получить дополнительную информацию, посмотрите это   -  person Hemant Parmar    schedule 22.01.2018
comment
Так что мне нужно дублировать раздел builScript во всех моих модулях?   -  person Martynas Jurkus    schedule 22.01.2018
comment
Позвольте нам продолжить это обсуждение в чате.   -  person Hemant Parmar    schedule 22.01.2018
comment
Добавьте пример проекта, который упростит задачу.   -  person Martynas Jurkus    schedule 22.01.2018
comment
Синтетика Kotlin устарела, поэтому рассмотрите возможность перехода на ViewBinding, который доступен в Android Gradle Plugin 3.6.0 (в настоящее время RC03). Это новый рекомендуемый Google способ доступа к представлениям, объявленным в XML макета.   -  person Mark    schedule 01.09.2020


Ответы (10)


Обновление:

Ссылки на синтетические представления устарели и не будут поддерживаться в будущем.

Текущее рекомендуемое решение - использовать ViewBinding (или дождаться Jetpack Compose)

Исходный ответ:

Один из способов решить эту проблему - создать свойство псевдонима, которое можно использовать из других модулей:

// SyntheticExports.kt
package com.example.synthetic.exported

import kotlinx.android.synthetic.main.layout_in_library.*

inline val Activity.exported_text_view get() = text_view

Затем на другом модуле:

// MainActivity.kt
import com.example.synthetic.exported.exported_text_view

exported_text_view.text = "example"

У нас это работает. Приходится создавать разные расширения для view, fragment и т. Д. Делать их вручную немного утомительно, но это простейший обходной путь, который мы нашли.

Дополнительно: это также хороший способ экспортировать синтетические расширения как часть публичной библиотеки, а не только во внутренний модуль.

person pablisco    schedule 05.09.2019

Ни один из приведенных выше ответов мне не помог. Добавьте оба этих плагина в файл build.gradle на уровне вашего приложения:

apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'

Это должно решить проблему.

OR

plugins {
    id 'kotlin-android-extensions'
    id 'kotlin-android'
}
person Medha    schedule 12.10.2020
comment
Это правильный ответ - person Mahmoud Ayman; 30.11.2020
comment
@Medha Было бы неплохо, если бы вы могли быть более конкретными. Если я не ошибаюсь, вам следует добавить следующую строку в файл build.gradle для модуля (в отличие от проекта): id 'kotlin-android-extensions' в фигурных скобках для плагина. - person nayriz; 20.12.2020
comment
Спасибо @nayriz, отредактировал свой ответ - person Medha; 20.03.2021

У меня тоже возникла эта проблема, и мое решение:

  • Не использовать импорт kotlinx.android.synthetic.main....*

  • Используйте findViewById()

Например, я изменил с

textview_id.text = "abc"

to

findViewById(R.id.textview_id).text = "abc"
person Anh Duy    schedule 10.01.2019
comment
Однозначно самое простое решение! - person Xerus; 19.01.2019
comment
Ну .... смысл всего этого не в том, чтобы использовать findViewById, верно? - person egonzal; 19.06.2020

вы можете попробовать переключиться на 1.2.30-eap-16 и добавить

androidExtensions {
    experimental = true
}

к вашему build.gradle.

person Ivan Morgillo    schedule 15.02.2018
comment
Я зарегистрировал проблему в системе отслеживания проблем Kotlin для этой проблемы. - person Martynas Jurkus; 15.02.2018
comment
Решение не сработало для меня. Пытался добавить экспериментальный флаг в библиотечный модуль с макетом. Это не сработало. Затем я также добавил флаг к модулю, который зависит от первого, содержащего макет. Это тоже не работает. Очевидно, JetBrains еще не устранила проблему: youtrack.jetbrains.com/issue/KT-22430 < / а> - person cesards; 05.11.2018
comment
androidExtensions { experimental = true } работал у меня - person raquezha; 16.05.2019

Решение намного проще, чем предполагалось, синтетические сахара Kotlin являются частью Kotlin Android Extensions, и вам просто нужно применить плагин вручную (Android Studio применяет это автоматически только к модулям приложений):

Добавьте следующее в сборку Gradle вашей библиотеки / функционального модуля:

apply plugin: 'kotlin-android-extensions'

Теперь наслаждайтесь синтетическим сахаром Kotlin в своем коде Kotlin! ;)

person HumbleBee    schedule 04.06.2020

Как упоминалось в комментарии @cesards выше, это текущая проблема с расширениями Kotlin для Android. И на сегодняшний день это не решено.


Хорошо: используйте настраиваемые представления

Мое основное предложение - инкапсулировать представление и соответствующее поведение как настраиваемое представление, и вместо того, чтобы включать его через тег <include>, используйте его непосредственно в вашем макете как <com.example.app.YourCustomView>.

Это будет работать независимо от того, находится ли ваш класс представления в другом модуле или нет.


Плохо и уродливо: обходной путь для столкновения идентификаторов

Однако я нашел обходной путь, если вы хотите получить только одну ссылку из включенного представления.

Выполните следующие действия, чтобы использовать синтетический импорт kotlin для макетов, включенных из других модулей:

  1. Укажите идентификатор представления, на которое вы хотите получить ссылку во включенном XML-файле.
  2. Дайте тот же идентификатор тегу включения в включающем XML-файле.
  3. Теперь синтетический импорт представления (id) из включаемого макета (не из включенного)

Я не совсем уверен, как и почему это работает, но это очень хрупкий и грязный способ повторного использования макетов.

Пример:

Включенный макет (fragment_example.xml)

<include
    android:id="@+id/exampleView"
    layout="@layout/example_layout" />

Включенный вами макет (example_layout.xml)

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

    <TextView
        android:id="@+id/exampleView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</merge>

Ваш класс фрагмента (ExampleFragment.kt)

import kotlinx.android.synthetic.main.fragment_example.exampleView

// Do not import the exampleView through example_layout.exampleView

class ExampleFragment : Fragment() {
    // Do something with imported exampleView
}
person SafaOrhan    schedule 31.12.2018
comment
поэтому я нашел это решение Bad and Ugly: Id Collision Workaround раньше, но вы можете найти ссылку не на TextView, а просто на View, поэтому exampleView.setText() недоступен - person kassim; 18.06.2019

Предварительные условия

Не импортируйте следующую библиотеку import kotlinx.android.synthetic.main.my_view.view.*

app / MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val view: View = MyView(this)
        view.findViewById<TextView>(R.id.textViewPocLib).text = "I can edit the library components"
        setContentView(view)
}

my_library / MyView.kt

class MyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    LinearLayout(context, attrs, defStyleAttr) {

    init {
        LayoutInflater.from(context)
            .inflate(R.layout.my_view, this, true)
    }
}

GL

Источники:

person Braian Coronel    schedule 25.07.2019

Мой сценарий был: мне нужны ссылки на представления базового класса (из библиотеки) в дочерних классах (модуль приложения), и у меня было минимальное количество ссылок на представления из базовых классов библиотечного модуля.

Я бы сказал, это своего рода исправление, а не решение.

Решение №1 Получите ссылку на представление из базового класса

//lib module snippet
import kotlinx.android.synthetic.main.view_user_input.*

class LibModuleBaseActivity : AppCompatActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.view_user_input)
   }

    protected val vFirstName: TextView by lazy { tvFirstName }
    ...........
}


//app module snippet
class AppModuleActivity : LibModuleBaseActivity() {

    fun setUserDetails(name: String) {
        vFirstName.text = name
    }
}

Решение №2 Выполняйте задачу с помощью Base Class

//lib module snippet
import kotlinx.android.synthetic.main.view_user_input.*

class LibModuleBaseActivity : AppCompatActivity() {
    protected fun setUserDetails(name: String) {
        tvFirstName.text = name
    }
    ...........
}


//app module snippet
class AppModuleActivity : LibModuleBaseActivity() {
    ............
    setUserDetails("user_name")
}

Примечание. Это работает только в том случае, если вы наследуете модуль lib и ваш базовый класс расширяет представления.

person Bharatesh    schedule 21.11.2019
comment
Думаю, это может привести к утечке памяти - person Martynas Jurkus; 27.11.2019

В порядке постепенного улучшения решения @ pablisco, приведенного выше, в моей библиотеке есть файл:

// SyntheticExports.kt
@file:Suppress("ClassName")

package com.example.library.synthetic.main

import android.view.TextView
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.common_layout.view.rootLayout as syntheticRootLayout
import kotlinx.android.synthetic.main.common_layout.view.retryButton as syntheticRetryButton

object common_layout {
    object view {
        inline val View.rootLayout: ViewGroup get() = syntheticRootLayout
        inline val View.retryButton: TextView get() = syntheticRetryButton
    }
}

Затем на другом модуле:

// MainActivity.kt
import com.example.library.synthetic.main.common_layout.view.rootLayout
import com.example.library.synthetic.main.common_layout.view.retryButton

rootView.rootLayout.isVisible = true
rootView.retryButton.setOnClickListener { doIt() }

Если эта проблема когда-либо будет устранена, мне просто нужно изменить импорт, чтобы он начинался с «kotlinx.android» вместо «comp.example.library».

person vonWippersnap    schedule 25.05.2020

Я решил проблему, скопировав как androidExtension, так и buildscript из проекта build.gradle в модуль, использующий эту библиотеку.

buildscript {

    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:$gradle_version"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

androidExtensions {
    experimental = true
}

Примечание: я использую следующую версию kotlin и gradle:

buildscript {
    ext.kotlin_version = '1.3.0'
    ext.gradle_version = '3.0.1'
person I Made Mudita    schedule 09.11.2018