Ошибка "Not enough information to infer parameter T" в Kotlin при работе с Android 8.0
Проблема вывода типа в дженериках Kotlin
Ошибка "Not enough information to infer parameter T" возникает не из-за Android 8.0, а из-за особенностей системы вывода типов в Kotlin. Даже если вы явно указываете тип возвращаемой переменной (val name: String), компилятору не всегда достаточно этой информации для вывода типа T в функции.
Почему это происходит?
Проблема в том, что Kotlin выводит тип T на основе аргументов функции. В вашем случае аргумент default имеет тип T, но компилятор не может однозначно определить T только по значению по умолчанию, потому что:
- Литерал
"default_name"сам по себе не гарантирует, чтоT— этоString(он может бытьAnyилиCharSequence). - Компилятор не "смотрит вперёд" на тип переменной, в которую присваивается результат. Он сначала пытается вывести тип из аргументов, а потом уже проверяет совместимость.
Решение: явное указание типа при вызове (самый надёжный способ)
Самый простой и читаемый способ — явно указать тип при вызове функции:
val name: String = getFromPrefs<String>("user_name", "default_name")
val age: Int = getFromPrefs<Int>("user_age", 0)
Это полностью решает проблему, и код становится однозначным. Не бойтесь избыточности — это стандартная практика в Kotlin для дженериков.
Альтернативное решение: перегрузка функций (более элегантный подход)
Если вы хотите избежать явного указания типа, лучше использовать перегрузку функций для каждого поддерживаемого типа:
fun getStringFromPrefs(key: String, default: String): String {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return prefs.getString(key, default)
}
fun getIntFromPrefs(key: String, default: Int): Int {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return prefs.getInt(key, default)
}
fun getBooleanFromPrefs(key: String, default: Boolean): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return prefs.getBoolean(key, default)
}
Теперь вызов будет без ошибок:
val name = getStringFromPrefs("user_name", "default_name")
val age = getIntFromPrefs("user_age", 0)
Продвинутое решение: inline-функция с reified
Если вам очень нужен один универсальный метод, используйте inline и reified — это позволит компилятору знать тип во время выполнения:
inline fun <reified T> getFromPrefs(key: String, default: T): T {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return when (T::class) {
String::class -> prefs.getString(key, default as String) as T
Int::class -> prefs.getInt(key, default as Int) as T
Boolean::class -> prefs.getBoolean(key, default as Boolean) as T
else -> default
}
}
Теперь вызов работает без ошибок:
val name = getFromPrefs("user_name", "default_name") // T выводится как String
Важно: reified работает только с inline функциями. Это добавит немного накладных расходов на встраивание кода, но для SharedPreferences это некритично.
Рекомендация
Для новичков советую использовать перегрузку функций — это самый понятный и безопасный подход. Если вы уверены в своих знаниях — используйте reified. Явное указание типа — тоже хороший вариант, но он делает код менее читаемым при частых вызовах.
Дополнительный подход: использование делегатов свойств
В Kotlin есть элегантный способ работы с SharedPreferences через делегаты свойств. Это полностью решает проблему вывода типов и делает код чище:
class PrefsManager(context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var userName: String by Delegates.prefsDelegate(prefs, "user_name", "default_name")
var userAge: Int by Delegates.prefsDelegate(prefs, "user_age", 0)
var isLoggedIn: Boolean by Delegates.prefsDelegate(prefs, "is_logged_in", false)
}
// Вспомогательный объект для делегата
object Delegates {
fun <T> prefsDelegate(prefs: SharedPreferences, key: String, default: T) = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (default) {
is String -> prefs.getString(key, default) as T
is Int -> prefs.getInt(key, default) as T
is Boolean -> prefs.getBoolean(key, default) as T
else -> default
}
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
when (value) {
is String -> prefs.edit().putString(key, value).apply()
is Int -> prefs.edit().putInt(key, value).apply()
is Boolean -> prefs.edit().putBoolean(key, value).apply()
}
}
}
}
Теперь вызов выглядит так:
val prefsManager = PrefsManager(this)
val name = prefsManager.userName // Тип String выводится автоматически
prefsManager.userAge = 25 // Запись тоже работает
Этот подход не только решает проблему вывода типа, но и инкапсулирует логику работы с SharedPreferences, делая код более модульным и тестируемым. Рекомендую для проектов среднего размера и выше.