Martynas Jurkus
Martynas Jurkus

Reputation: 9301

Unresolved reference for synthetic view when layout is in library module

using Kotlin 1.20.20 (not that it matters, older versions behaves the same)

When layout is in separate library module Android Studio has no problem finding and referencing the view

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

But when I try to compile app it fails with Unresolved reference: view_user_input on :app:compileDebugKotlin.

Everything works fine when view is referenced IN the library module.

Am I missing something here?

Adding project structure. All modules are using kotlin and kotlin-extensions.

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

Here is an example app https://github.com/mjurkus/KotlinxTest

Registered issue for that in KT tracker

Upvotes: 49

Views: 24753

Answers (10)

Medha
Medha

Reputation: 261

None of the above answers actually helped me . Add both these plugins in your app level build.gradle file on top :

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

This should solve the issue .

OR

plugins {
    id 'kotlin-android-extensions'
    id 'kotlin-android'
}

Upvotes: 10

pablisco
pablisco

Reputation: 14237

Update:

Synthetic view references have been deprecated and will not be supported in the future.

The current recommended solution is to use ViewBinding (or wait for Jetpack Compose)

Original Answer:

One way to solve this is to create an "alias" property that can be consumed from other modules:

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

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

inline val Activity.exported_text_view get() = text_view

Then on the other module:

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

exported_text_view.text = "example"

That works for us. Have to create different extensions for view, fragment, etc. It's a bit tedious to have to do them manually but this is the simplest workaround we found.

Extra: This is also a decent method to export synthetic extensions as part of a public library too, not just in an internal module.

Upvotes: 23

HumbleBee
HumbleBee

Reputation: 1423

The solution is far simpler than imagined, Kotlin synthetic sugars are a part of Kotlin Android Extensions and you just have to apply the plugin manually (Android Studio applies this automatically only to application modules):

Add the following to your library / feature module's build Gradle:

apply plugin: 'kotlin-android-extensions'

Now enjoy Kotlin synthetic sugars in your Kotlin code! ;)

Upvotes: 3

vonWippersnap
vonWippersnap

Reputation: 523

In an incremental improvement on @pablisco's solution above, in my library I have a file:

// 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
    }
}

Then on the other module:

// 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() }

If this issue is ever fixed, I just need to change imports to start with "kotlinx.android" instead of "comp.example.library".

Upvotes: 0

Ivan Morgillo
Ivan Morgillo

Reputation: 3844

you could try switching to 1.2.30-eap-16 and adding

androidExtensions {
    experimental = true
}

to your build.gradle.

Upvotes: 2

Anh Duy
Anh Duy

Reputation: 1183

I also got this issue and my solution is:

  • Don't use import kotlinx.android.synthetic.main....*

  • Use findViewById()

For example I changed from

textview_id.text = "abc"

to

findViewById(R.id.textview_id).text = "abc"

Upvotes: 4

Bharatesh
Bharatesh

Reputation: 9009

My scenario was : I needed base class (from lib) view references in child classes (app module) and had minimum number of view references from Library Module's base classes.

I would say, it's kind a fix and not a solution.

Solution #1 Get a view reference from Base class

//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
    }
}

Solution #2 Get the task done by 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")
}

Note: This works only if you are inheriting from lib module and where your Base class is inflating views.

Upvotes: 0

Braian Coronel
Braian Coronel

Reputation: 22877

Preconditions

Do not import the following library 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

Sources:

Upvotes: 1

SafaOrhan
SafaOrhan

Reputation: 720

As mentioned in the comment by @cesards above this is an on going issue with the Kotlin Android Extentions. And it is not solved as of today.


Good: Use Custom Views

My primary suggestion is encapsulating the view and the relevant behavior as a custom view and instead of including it via <include> tag, use it directly in your layout as <com.example.app.YourCustomView>.

This will work no matter your view class is in another module or not.


Bad and Ugly: Id Collision Workaround

However I found a hacky workaround if you only want to get one reference from the included view.

Follow these steps to use kotlin synthetic imports for the layouts included from other modules:

  1. Give an id to the view you want to get reference of in the included xml file.
  2. Give the same id to the include tag in the including xml file.
  3. Now synthetic import the view (id) from the including layout (not from the included)

I'm not really sure how and why this works but it's a very fragile and dirty way of reusing layouts.

Example:

Your including layout (fragment_example.xml)

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

Your included 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>

Your fragment class (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
}

Upvotes: 1

I Made Mudita
I Made Mudita

Reputation: 570

I solved my issue with copy both androidExtension and buildscript from project build.gradle to module that uses the library.

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
}

Note: I use the following version of kotlin and gradle:

buildscript {
    ext.kotlin_version = '1.3.0'
    ext.gradle_version = '3.0.1'

Upvotes: -2

Related Questions