Avatar ·

Ошибка setVisibility в Android P: метод можно вызывать только из той же библиотечной группы

Всем привет! Я разрабатываю приложение на Kotlin для Android и столкнулся с очень странной ошибкой при попытке скрыть или показать кнопку `VisibilityAwareImageButton` на Android 9 (Pie). У меня есть простой фрагмент кода: ```kotlin class MyFragment : Fragment() { private lateinit var myButton: VisibilityAwareImageButton override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) myButton = view.findViewById(R.id.my_button) // Пытаюсь скрыть кнопку myButton.visibility = View.GONE } } ``` При запуске на Android 9 (API 28) я получаю исключение: ``` java.lang.RuntimeException: VisibilityAwareImageButton.setVisibility can only be called from the same library group (groupId=com.google.android.material) ``` Я использую Material Design Components (com.google.android.material:material:1.2.0) и AndroidX. Кнопка объявлена в XML как: ```xml ``` Почему эта ошибка возникает? Ведь раньше на Android 8 всё работало нормально. Я слышал, что это связано с ограничениями библиотечных групп в Android 9, но не понимаю, как это исправить. Подскажите, что делать? Заранее спасибо! <<>>

Причина ошибки

Ошибка VisibilityAwareImageButton.setVisibility can only be called from the same library group возникает из-за механизма изоляции библиотечных групп (Library Groups), введённого в Android 9.0 Pie. Система проверяет, что методы, изменяющие состояние виджетов из библиотеки Material Design (группа com.google.android.material), вызываются только из кода, принадлежащего той же библиотечной группе. Это сделано для предотвращения некорректного изменения состояния компонентов из стороннего кода.

Проблема в том, что VisibilityAwareImageButton — это внутренний класс Material Components, который наследуется от AppCompatImageButton. Когда вы пишете myButton.visibility = View.GONE, вы вызываете сеттер setVisibility(), который в этом классе переопределён с проверкой на библиотечную группу. Если ваш код находится в приложении (группа по умолчанию), а не в самой библиотеке Material, система блокирует вызов.

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

Вместо прямого обращения к свойству visibility используйте методы, предоставляемые Material Components, которые корректно обрабатывают эту проверку:

// Вместо этого:
myButton.visibility = View.GONE

// Используйте это:
myButton.isVisible = false  // для MaterialButton
// или
myButton.hide()  // если есть метод hide()

Для MaterialButton (который вы используете в XML) правильный способ — работать через свойство isVisible или методы show()/hide():

class MyFragment : Fragment() {
    private lateinit var myButton: MaterialButton

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        myButton = view.findViewById(R.id.my_button)
        
        // Правильный способ:
        myButton.isVisible = false  // скрыть кнопку
        // или
        myButton.hide()  // альтернативный метод
    }
}

Почему это работает?

Методы show(), hide() и свойство isVisible внутри себя вызывают setVisibility() с правильным контекстом библиотечной группы. Они являются частью публичного API Material Design и не вызывают ошибку изоляции.

Дополнительные рекомендации

  • Всегда используйте тип MaterialButton вместо VisibilityAwareImageButton в своём коде. Если вы получаете кнопку через findViewById, указывайте правильный тип.
  • Обновите зависимости — убедитесь, что вы используете последнюю версию Material Components (например, 1.6.0+), где такие баги исправлены.
  • Избегайте прямого доступа к внутренним классам — VisibilityAwareImageButton — это внутренний класс библиотеки, его не следует использовать напрямую.

Если проблема остаётся

В редких случаях может потребоваться обновить Android SDK и пересобрать проект. Также проверьте, что в вашем build.gradle правильно указана зависимость:

dependencies {
    implementation 'com.google.android.material:material:1.9.0'  // последняя версия
}

После применения этих изменений ошибка должна исчезнуть, и кнопка будет корректно скрываться/показываться на Android 9 и выше.

<<>>

Альтернативный подход: Использование View.GONE через рефлексию

Если по какой-то причине вы не можете использовать методы Material Components (например, работаете с кастомным View, который наследуется от VisibilityAwareImageButton), можно обойти ограничение библиотечных групп с помощью рефлексии. Однако это не рекомендуется для production-кода, так как нарушает архитектуру библиотеки и может привести к нестабильности.

fun setVisibilitySafely(view: View, visibility: Int) {
    try {
        val method = View::class.java.getMethod("setVisibility", Int::class.java)
        method.isAccessible = true
        method.invoke(view, visibility)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

// Использование:
setVisibilitySafely(myButton, View.GONE)

Лучший вариант: Использовать View.animate()

Ещё один способ — анимировать изменение видимости через animate(), который также работает с системой библиотечных групп:

myButton.animate().alpha(0f).withEndAction {
    myButton.visibility = View.GONE  // теперь это сработает, так как вызывается из того же потока после анимации
}

Однако я настоятельно рекомендую придерживаться первого ответа — использовать публичные методы Material Components. Рефлексия и анимации — это лишь временные костыли, которые могут сломаться в будущих версиях библиотеки.