Introducción: «¡Hice todo según el tutorial, pero la aplicación se cierra!»
\nSi recién estás comenzando tu camino en el desarrollo de Android con Kotlin, seguramente ya te has encontrado con este momento. Escribes código siguiendo un tutorial, ejecutas la aplicación… y crash. En los registros aparece el famoso java.lang.NullPointerException (o su equivalente en Kotlin). Y el mensaje: «Cannot read field… because the view is null».
Este es un dolor real que ha acumulado miles de visitas en Stack Overflow. Hoy lo analizaremos de una vez por todas. ¿Por qué ocurre el NullPointerException al acceder a View en un Fragmento en Kotlin? Y, lo más importante, cómo solucionarlo para que puedas dormir tranquilo.
\n¡Allá vamos!
\n\n¿Por qué ocurre el error? La raíz del mal: el Lifecycle
\nLa razón principal por la que los principiantes obtienen un NullPointerException en un Fragmento de Kotlin es la falta de comprensión del ciclo de vida del fragmento. En una Activity es simple: onCreate y listo. En un Fragmento es más complicado.
El fragmento pasa por varias etapas:
\n- \n
onCreateView()— aquí se infla (crea) el layout. \n onViewCreated()— aquí la View ya existe con certeza. \n onDestroyView()— la View se destruye (por ejemplo, al rotar la pantalla). \n
El error más común es intentar acceder a la View antes de que el fragmento haya creado su layout. Por ejemplo, en el constructor o en onCreate() del fragmento. En ese momento, view es igual a null — de ahí el crash.
La segunda razón es la fuga de referencias. Guardaste una referencia a la View en una variable, luego el fragmento se recrea y la referencia antigua no lleva a ninguna parte.
\n\nEjemplo típico: cómo se ve el problema en el código
\nImagina que tienes un fragmento simple con un campo de texto:
\nclass MyFragment : Fragment() {\n\n private lateinit var myTextView: TextView\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n // ¡ERROR! Aquí la View aún no existe\n myTextView = requireView().findViewById(R.id.my_text_view)\n myTextView.text = "¡Hola!"\n }\n\n override fun onCreateView(\n inflater: LayoutInflater,\n container: ViewGroup?,\n savedInstanceState: Bundle?\n ): View? {\n return inflater.inflate(R.layout.fragment_my, container, false)\n }\n}\nEste código fallará con un NullPointerException, porque requireView() en onCreate() devuelve null. ¡La View aún no ha sido creada!
3 formas prácticas de solucionar el error
\nVeamos tres enfoques que resuelven garantizadamente el problema del acceso a View en un Fragmento.
\n\nForma 1: Mover el código a onViewCreated()
\nLa forma más simple y correcta es esperar al momento en que la View exista con certeza. El método onViewCreated() se llama inmediatamente después de crear el layout.
class MyFragment : Fragment() {\n\n private lateinit var myTextView: TextView\n\n override fun onCreateView(\n inflater: LayoutInflater,\n container: ViewGroup?,\n savedInstanceState: Bundle?\n ): View? {\n return inflater.inflate(R.layout.fragment_my, container, false)\n }\n\n override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n super.onViewCreated(view, savedInstanceState)\n // ¡Ahora view definitivamente no es null!\n myTextView = view.findViewById(R.id.my_text_view)\n myTextView.text = "¡Todo funciona!"\n }\n}\nVentajas: simple, claro, funciona siempre.
\nDesventajas: hay que recordar usar lateinit var y no olvidar anular las referencias en onDestroyView() (de lo contrario, puede haber una fuga de memoria).