Несогласованность порядка внедрения рукоятки, приводящая к исключениям NullPointerExceptions в Compose ViewModelAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Несогласованность порядка внедрения рукоятки, приводящая к исключениям NullPointerExceptions в Compose ViewModel

Сообщение Anonymous »

В настоящее время я работаю над приложением, используя Compose и Hilt, и иногда (менее 10 раз из 1000 согласно моим автоматическим тестам) порядок вызовов моей модели ViewModel отличается, что приводит к исключениям NullPointerException, потому что я манипулирую атрибуты, которые Hilt, очевидно, еще не внедрил.
Мои модели представления наследуются от базовой модели представления:

Код: Выделить всё

abstract class ArrowWordsViewModel(
private val dispatcher: CoroutineDispatcher = Dispatchers.IO.also { println("CPT ERROR Parent constructor") }
) :
ViewModel() {

init {
println("CPT ERROR INIT")
computeViewModelInternally(true)
}

open suspend fun computeViewModel() {}

private fun computeViewModelInternally() {
Timber.d("CPT ERROR - computeViewModelInternal")

viewModelScope.launch(dispatcher + CoroutineExceptionHandler { _, throwable ->
viewModelScope.launch(Dispatchers.Main) {
println("CPT ERROR THROWABLE- ${throwable.javaClass.name}")
}
}) {
println("CPT ERROR computeViewModel parent")
computeViewModel()
}
}
}
И у меня есть ViewModel, которая наследуется от нее. Например:

Код: Выделить всё

@HiltViewModel
class AdsViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val preferences: ArrowWordsPreferences,
private val fake: Int
) : ArrowWordsViewModel() {

val isOnBoardingContextTest= savedStateHandle.get(ScreenArgs.ADS_IS_ONBOARDING_EXTRA).also { println("CPT ERROR ATTRIBUTE") }

val isOnBoardingContext = MutableStateFlow(false)

val showAlertDialog = MutableStateFlow(false)

override suspend fun computeViewModel() {

println("CPT ERROR computeViewModel")

super.computeViewModel()

isOnBoardingContext.value = savedStateHandle.get(ScreenArgs.ADS_IS_ONBOARDING_EXTRA) ?: false
}

}
Внедренные элементы определяются в модуле Hilt:

Код: Выделить всё

@Module
@InstallIn(SingletonComponent::class)
class AppModule {

@Provides
@Singleton
fun providePreferences(@ApplicationContext context: Context): ArrowWordsPreferences =
ArrowWordsPreferences(context)

@Provides
fun provideFake() : Int =
1.also { println("CPT ERROR - PROVIDE FAKE") }
}
Моя ViewModel затем используется в Composable:

Код: Выделить всё

@Composable
fun AdsScreen(
modifier: Modifier = Modifier,
viewModel: AdsViewModel = hiltViewModel(),
onBackClicked: () -> Unit,
onOnBoardingFinished: () -> Unit
) {
//...
}
В 99% случаев все работает нормально. Но иногда я получаю исключение NullPointerException в строке isOnBoardingContext.value = saveStateHandle.get(ScreenArgs.ADS_IS_ONBOARDING_EXTRA) ?: false, вызываемой в методе ComputeViewModel AdsViewModel class.
Как видите, я добавил логи и поддельные инъекции, чтобы попытаться понять поток моего кода.
В 99% случаев, когда все идет хорошо, логи выдают следующий результат:

Код: Выделить всё

CPT ERROR - PROVIDE FAKE
CPT ERROR Parent constructor
CPT ERROR INIT
CPT ERROR ATTRIBUTE
CPT ERROR computeViewModel parent
CPT ERROR computeViewModel
Но когда я получаю исключение NullPointerException, отображаются следующие журналы:

Код: Выделить всё

CPT ERROR - PROVIDE FAKE
CPT ERROR Parent constructor
CPT ERROR INIT
CPT ERROR computeViewModel parent
CPT ERROR computeViewModel
CPT ERROR ATTRIBUTE
CPT ERROR THROWABLE- java.lang.NullPointerException
Мы видим, что разница находится на уровне журнала CPT ERROR ATTRIBUTE, который находится не в том же месте.
Согласно документации Kotlin:

Во время инициализации экземпляра блоки инициализатора выполняются в том же порядке, в котором они появляются в теле класса, чередуясь с инициализаторы свойств:

Поэтому я делаю вывод, что в моем случае конструктор класса ArrowWordsViewModel вызывается ДО окончания внедрения Hilt в конструктор класса AdsViewModel . Поскольку вызывается основной конструктор класса ArrowWordsViewModel, он затем разрешает вызов блока init, который запускает методы ComputeViewModel и приводит к NullPointerExceptions, поскольку Hilt этого не делает. кажется, завершил внедрение (поскольку атрибуты класса AdsViewModel вызываются позже).
Как я могу переработать свой код, чтобы гарантировать, что Hilt завершил внедрение перед вызовом блок init родительского класса, который запускает загрузку данных в моем случае?
Обратите внимание, что код намеренно упрощен, и я хотел бы сохранить эту систему «функций, которые загружает данные", если это возможно.

Подробнее здесь: https://stackoverflow.com/questions/792 ... -compose-v
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»