Kotlin lateinit properties, peligro NPE?

Estoy usando properties lateinit para evitar la verificación nula continua con el? operador. Tengo muchas properties de Vista que se asignan por primera vez en la function getViews (). Si esa function no estuviera allí, mi aplicación fallaría con un NPE, de un código de Kotlin.

En mi opinión, las properties lateinit básicamente arruinan las buenas características de security nula del lenguaje. Sé que se presentan en M13 debido a un mejor soporte de framework, pero ¿vale la pena?

¿O me estoy perdiendo algo aquí?

Aquí está el código:

package com.attilapalfi.exceptional.ui import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.View import android.widget.Button import android.widget.ImageView import android.widget.TextView import com.attilapalfi.exceptional.R import com.attilapalfi.exceptional.dependency_injection.Injector import com.attilapalfi.exceptional.model.Exception import com.attilapalfi.exceptional.model.ExceptionType import com.attilapalfi.exceptional.model.Friend import com.attilapalfi.exceptional.persistence.* import com.attilapalfi.exceptional.rest.ExceptionRestConnector import com.attilapalfi.exceptional.ui.helpers.ViewHelper import com.attilapalfi.exceptional.ui.question_views.QuestionYesNoClickListener import com.google.android.gms.maps.MapView import java.math.BigInteger import javax.inject.Inject public class ShowNotificationActivity : AppCompatActivity(), QuestionChangeListener { @Inject lateinit val exceptionTypeStore: ExceptionTypeStore @Inject lateinit val friendStore: FriendStore @Inject lateinit val imageCache: ImageCache @Inject lateinit val metadataStore: MetadataStore @Inject lateinit val viewHelper: ViewHelper @Inject lateinit val exceptionInstanceStore: ExceptionInstanceStore @Inject lateinit val exceptionRestConnector: ExceptionRestConnector @Inject lateinit val questionStore: QuestionStore private lateinit var sender: Friend private lateinit var exception: Exception private lateinit var exceptionType: ExceptionType private lateinit var exceptionNameView: TextView private lateinit var exceptionDescView: TextView private lateinit var senderImageView: ImageView private lateinit var senderNameView: TextView private lateinit var sendDateView: TextView private lateinit var mapView: MapView private lateinit var questionText: TextView private lateinit var noButton: Button private lateinit var yesButton: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_show_notification) Injector.INSTANCE.applicationComponent.inject(this) questionStore.addChangeListener(this) getModelFromBundle() getViews() loadViewsWithData() } override fun onDestroy() { super.onDestroy() questionStore.removeChangeListener(this) } private fun getModelFromBundle() { val bundle = intent.extras val instanceId = BigInteger(bundle.getString("instanceId")) exception = exceptionInstanceStore.findById(instanceId) sender = friendStore.findById(exception.fromWho) exceptionType = exceptionTypeStore.findById(exception.exceptionTypeId) } private fun getViews() { exceptionNameView = findViewById(R.id.notif_full_exc_name) as TextView exceptionDescView = findViewById(R.id.notif_exc_desc) as TextView senderImageView = findViewById(R.id.notif_sender_image) as ImageView senderNameView = findViewById(R.id.notif_sender_name) as TextView sendDateView = findViewById(R.id.notif_sent_date) as TextView mapView = findViewById(R.id.notif_map) as MapView questionText = findViewById(R.id.notif_question_text) as TextView noButton = findViewById(R.id.notif_question_no) as Button yesButton = findViewById(R.id.notif_question_yes) as Button } private fun loadViewsWithData() { exceptionNameView.text = exceptionType.prefix + "\n" + exceptionType.shortName exceptionDescView.text = exceptionType.description imageCache.setImageToView(sender, senderImageView) senderNameView.text = viewHelper.getNameAndCity(exception, sender) sendDateView.text = exception.date.toString() loadQuestionToViews() } private fun loadQuestionToViews() { if (exception.question.hasQuestion) { showQuestionViews() } else { hideQuestionViews() } } private fun showQuestionViews() { questionText.text = exception.question.text val listener = QuestionYesNoClickListener(exception, exceptionRestConnector, noButton, yesButton) noButton.setOnClickListener(listener) yesButton.setOnClickListener(listener) } private fun hideQuestionViews() { questionText.visibility = View.INVISIBLE noButton.visibility = View.INVISIBLE yesButton.visibility = View.INVISIBLE } override fun onQuestionsChanged() { onBackPressed() } } 

La misma característica básica de lateinit era realmente posible con Delegates.notNull antes de M13.

Hay otras características que también le permiten omitir los requisitos de anulabilidad. El !! el operador convertirá un valor que admite valores NULL en un valor no nulo.

El objective no es estrictamente exigir restricciones de nulabilidad, sino hacer que la nulidad sea una parte muy explícita del lenguaje. ¡Cada vez que usas lateinit o !! está tomando una decisión consciente para dejar la security de las restricciones de nulabilidad, con suerte, con buenas razones.

Como regla general, lo mejor es evitar lateinit , !! e incluso ? (nullable) tanto como sea posible.

Muchas veces puede usar un delegado perezoso para evitar retrasos que pueden mantenerlo en el ámbito de vals no nulos (M14 ahora prohíbe usar vals con lateinit ).

El código al que vinculó incluye una gran cantidad de vistas tardías. Podrías mantener estos como valores no nulos haciendo algo como esto:

 private val mapView: MapView by lazy { findViewById(R.id.notif_map) as MapView } 

Esto inicializará el valor de la primera vez que se use mapView y usará el valor previamente inicializado a partir de entonces. La advertencia es que esto podría romperse si intenta utilizar mapView antes de la llamada a setContentView . Sin embargo, eso no parece tan importante y usted ha obtenido el beneficio de que su initialization está justo al lado de su statement.

Esto es lo que la biblioteca de kotterknife usó para lograr la inyección de vista.

La delegación perezosa de Kotlin funcionará en muchos casos, aunque te encontrarás con problemas al volver a cargar los Fragmentos que se han guardado en FragmentManager. Cuando el sistema Android reconstruye el fragment, en realidad recreará la vista y hará que una view?.findViewById(R.id.notif_map) devuelva una Vista no válida.

En estos casos, deberá usar una propiedad de solo lectura:

 private val mapView: MapView get() = view?.findViewById(R.id.notif_map) as MapView 
  • Diferencia entre varias funciones verifySignatures en la testing de unidad de flujo Corda
  • Tienda lambda en una variable en kotlin
  • Kotlin: método no puede ser utilizado con generics
  • Dagger 2 con Kotlin, regresando el tipo con generic en ApplicationComponent
  • Cómo analizar JSON desde una url usando Kotlin en Android?
  • Cómo crear una list inmutable que debe recorrer un campo de otra list
  • GoogleApiClient: No se puede conectar y ejecutar manualmente el inicio de session después
  • Kotlin: método genérico y para bucle preguntando por iterador ()
  • Múltiple variable en Kotlin
  • Forma idiomática de secuencia dertwigda en tres lists usando Kotlin
  • ¿Cómo establecer "sourceCompatibility" para Kotlin y Gradle?