Ошибка "Default interface methods are only supported starting with Android 7.0 (Nougat)" в Kotlin
Всем привет! Я новичок в Kotlin и Android-разработке, столкнулся с непонятной ошибкой при компиляции проекта. Использую Android Studio, targetSdkVersion 34, minSdkVersion 21.
У меня есть интерфейс с методом по умолчанию:
interface PaymentCallback {
fun onSuccess(transactionId: String)
fun onError(error: String)
fun onProgress(percent: Int) {
// Дефолтная реализация - логируем прогресс
Log.d("Payment", "Progress: $percent%")
}
}
И класс, который его реализует:
class MainActivity : AppCompatActivity(), PaymentCallback {
override fun onSuccess(transactionId: String) {
Toast.makeText(this, "Оплата успешна: $transactionId", Toast.LENGTH_LONG).show()
}
override fun onError(error: String) {
Toast.makeText(this, "Ошибка: $error", Toast.LENGTH_LONG).show()
}
// onProgress не переопределяем, используем дефолтную реализацию
}
При сборке получаю ошибку: "Default interface methods are only supported starting with Android 7.0 (Nougat)". Я читал, что Kotlin компилируется в Java-байткод, и эта фича требует API 24+. Но ведь многие библиотеки используют дефолтные методы интерфейсов (например, в Java 8), и они работают на старых устройствах. Как быть? Неужели придется поднимать minSdkVersion до 24? Или есть какой-то обходной путь? Буду благодарен за подробное объяснение.
<<Понимание проблемы и её решение
Ошибка возникает из-за того, что дефолтные методы в интерфейсах (default interface methods) — это фича Java 8, которая в Android поддерживается только начиная с API 24 (Android 7.0 Nougat). Kotlin генерирует Java-байткод, и когда вы объявляете метод с реализацией в интерфейсе, он транслируется именно в default-метод Java. Если ваш minSdkVersion ниже 24, компилятор выдаёт эту ошибку.
Однако это не значит, что вы не можете использовать такую функциональность на старых устройствах. Есть несколько проверенных способов обойти ограничение.
Способ 1: Использовать Kotlin-плагин для десахаризации (D8/R8)
Современные версии Android Gradle Plugin (начиная с 4.0.0) поддерживают десахаризацию (desugaring) — процесс, при котором компилятор преобразует Java 8+ фичи в совместимый код для старых API. Включите её в build.gradle:
android {
compileOptions {
// Включаем поддержку Java 8
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
// Обязательно добавить эту зависимость
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}
После этого дефолтные методы интерфейсов будут работать на любом API, начиная с 21. Десахаризация происходит на этапе компиляции — D8/R8 преобразует вызовы default-методов в статические helper-методы.
Способ 2: Заменить интерфейс на абстрактный класс
Если по каким-то причинам десахаризация недоступна (например, старая версия Gradle), используйте абстрактный класс вместо интерфейса:
abstract class PaymentCallback {
abstract fun onSuccess(transactionId: String)
abstract fun onError(error: String)
open fun onProgress(percent: Int) {
Log.d("Payment", "Progress: $percent%")
}
}
class MainActivity : AppCompatActivity(), PaymentCallback() {
// ... реализация
}
Минус — вы теряете возможность множественного наследования интерфейсов.
Способ 3: Использовать Kotlin extension-функции
Можно вынести дефолтную логику в extension-функцию:
interface PaymentCallback {
fun onSuccess(transactionId: String)
fun onError(error: String)
fun onProgress(percent: Int) // Просто объявление, без реализации
}
fun PaymentCallback.onProgress(percent: Int) {
Log.d("Payment", "Progress: $percent%")
}
// В классе-реализаторе:
class MainActivity : AppCompatActivity(), PaymentCallback {
override fun onSuccess(transactionId: String) { ... }
override fun onError(error: String) { ... }
// onProgress не переопределяем, вызываем extension
fun someMethod() {
onProgress(50) // вызовется extension-функция
}
}
Но будьте осторожны: extension-функции — это статические методы, они не могут быть переопределены в подклассах. Если нужно переопределение, этот способ не подойдёт.
Рекомендация
Лучший способ — включить coreLibraryDesugaring. Он официально поддерживается Google, безопасен и не требует изменения архитектуры кода. Убедитесь, что у вас Android Gradle Plugin >= 4.0.0 и Gradle >= 6.1.1.
Если проект очень старый и обновить Gradle нельзя, используйте абстрактный класс — это самый простой и надёжный fallback.
<<Альтернативный подход: аннотация @RequiresApi и условная компиляция
Если вы не хотите подключать десахаризацию или менять архитектуру, можно пойти другим путём — аннотировать использование default-методов и обрабатывать их только на API 24+.
interface PaymentCallback {
fun onSuccess(transactionId: String)
fun onError(error: String)
@RequiresApi(Build.VERSION_CODES.N)
fun onProgress(percent: Int) {
Log.d("Payment", "Progress: $percent%")
}
}
class MainActivity : AppCompatActivity(), PaymentCallback {
override fun onSuccess(transactionId: String) { ... }
override fun onError(error: String) { ... }
// Вызываем только на Android 7.0+
fun handleProgress(percent: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
onProgress(percent)
} else {
// Fallback для старых устройств
Log.d("Payment", "Progress (fallback): $percent%")
}
}
}
Такой подход полезен, если дефолтный метод использует API, недоступные на старых версиях (например, java.time). Однако для простого логирования это избыточно. Главный минус — вы теряете единообразие: на старых устройствах поведение отличается, и код становится менее читаемым.
Поэтому я рекомендую использовать этот способ только для точечных случаев, когда десахаризация невозможна, и вы уверены, что различие в поведении допустимо.