Buenas prácticas usando Fragments en Android

Desde que Google incluyó los Fragments en Android, muchos desarrolladores no han sabido terminar de aclararse con el nuevo paradigma de diseño en las aplicaciones. Para los que no los conozcáis, un fragment es un objeto pensado para ser cargado desde un Activity y sustituir a este último como encargado de mostrar la interfaz e incluir el código de la interfaz gráfica. De este modo, dónde antes hacíamos un setContentView() en nuestro Activity, lo que ahora hacemos es «añadir» un Fragment al Activity, que es el que muestra el layout que queremos mostrar.

Esto lógicamente tiene muchas implicaciones, pero la principal ventaja que tiene con respecto al modelo Vista-Activity es que dentro de un solo Activity podemos lanzar varios Fragments que interaccionan entre ellos a través de su Activity, y de este modo nos ahorramos tener que estar todo el rato cambiando entre distintas actividades, con el consecuente gasto de memoria.

En cuanto a la creación de un Fragment, debemos conocer unas buenas prácticas que nos harán la vida mucho más sencilla y nos ahorrarán dolores de cabeza.

Creando los Fragments:

Para instanciar un Fragment, generalmente podemos hacer una llamada a new como haríamos con cualquier otro objeto:

	Fragment myFragment = new Fragment();

Sin embargo, imaginad que nuestro Fragment necesita unos parámetros que debemos pasarle desde nuestro Activity, lo cual podemos hacer utilizando el método setArguments(). Un error muy común y que es conducente a muchos fallos es crear el Fragment y luego utilizar setArguments() en algún momento de nuestra conveniencia, pero la documentación de Android es muy explicita en que setArguments() debe ser llamado nada más crear el Fragment, y si lo llamamos después los argumentos no se adjuntarán. ¿Qué hacemos pues? Sencillo, a la hora de construir nuestra clase Fragment, crearemos un constructor estático llamado newInstance() al que pasaremos los argumentos.

Veamos un ejemplo:

	public class ExampleFragment extends Fragment{
	
		public static final String TAG = "ExampleFragment";
		
		public static ExampleFragment newInstance(Bundle arguments){
			ExampleFragment f = new ExampleFragment();
			if(arguments != null){
				f.setArguments(arguments);
			}
			return f;
		}
		
		public ExampleFragment(){
		
		}
		
		// Resto de métodos del Fragment
	}

De ahora en adelante, cuando queramos crear un nuevo «ExampleFragment» lo que haremos será llamar al método ExampleFragment.newInstance(arguments) en lugar de llamar a new ExampleFragment(). De esta manera podremos pasar los parámetros al inicio, y aunque no adjuntemos el Fragment en este mismo momento, cuando lo hagamos tendremos los argumentos disponibles.

Otro dato importante es NUNCA ocultar la visibilidad del constructor vacío public ExampleFragment() ya que de hacerlo, nuestra aplicación provocará un fallo en ciertas ocasiones en las que debe recrear el Fragment.

Usando esta estructura de instanciación, cuando queramos añadir el Fragment a nuestro Activity, solo deberemos hacer lo siguiente:

	public class ExampleActivity extends Activity{
	
		@Override
		protected void onCreate(Bundle onSaveInstanceState){
			super.onCreate(onSaveInstanceState);
			String id = "IdQueNecesitaMyFragment";
			Bundle arguments = new Bundle();
			arguments.putString("id", id);
			ExampleFragment fragment = ExampleFragment.newInstance(arguments);
			FragmentTransaction ft = getFragmentManager().beginTransaction();
			ft.replace(android.R.id.content, fragment, ExampleFragment.TAG);
			ft.commit();
		}
	
	}

A partir de este momento, tendríamos disponible el parámetro «id» en el Fragment a través del método getArguments() y podríamos utilizarlo. Como he dicho antes, ahora es el Fragment el encargado de mostrar el layout e instanciar los objetos como botones, etc.

Veamos pues que es lo importante para usar el Fragment de ahora en adelante:

	public class ExampleFragment extends Fragment{
	
		public static final String TAG = "ExampleFragment";
		private FragmentIterationListener mCallback = null;
		public interface FragmentIterationListener{
			public void onFragmentIteration(Bundle parameters);
		}
		
		public static ExampleFragment newInstance(Bundle arguments){
			ExampleFragment f = new ExampleFragment();
			if(arguments != null){
				f.setArguments(arguments);
			}
			return f;
		}
		
		public ExampleFragment(){
		
		}
		
		//El fragment se ha adjuntado al Activity
		@Override
		public void onAttach(Activity activity) {
			super.onAttach(activity);
		}
		
		//El Fragment ha sido creado		
		@Override
		public void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
		}
		
		//El Fragment va a cargar su layout, el cual debemos especificar
		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
								 Bundle savedInstanceState) {
			super.onCreateView(inflater, container, savedInstanceState);		
		}
		
		//La vista de layout ha sido creada y ya está disponible
		@Override
		public void onViewCreated(View view, Bundle savedInstanceState) {
			super.onViewCreated(view, savedInstanceState);
		}
		
		//La vista ha sido creada y cualquier configuración guardada está cargada
		@Override
		public void onViewStateRestored(Bundle savedInstanceState) {
			super.onViewStateRestored(savedInstanceState);
		}
		
		//El Activity que contiene el Fragment ha terminado su creación
		@Override
		public void onActivityCreated(Bundle savedInstanceState) {
			super.onActivityCreated(savedInstanceState);
		}
		
		//El Fragment ha sido quitado de su Activity y ya no está disponible
		@Override
		public void onDetach() {
			super.onDetach();
		}
		
	}

En el código podéis ver comentado lo que hace cada método, estas son las callbacks más importantes que debemos sobreescribir para utilizar un Fragment, también tenemos otras disponibles que ya teníamos en el Activity como onStart() onResume() onPause() onStop() onDestroy() y onSaveInstanceState() que posiblemente tendremos que utilizar en algún momento.

Como podéis observar, en los Fragments tenemos muchos más métodos que nos permiten manejar la interfaz más claramente que los que teníamos en un Activity, dónde generalmente cargamos la vista en onCreate() e instanciábamos allí todos los objetos.

Mis consejos derivados de la experiencia para inicializar los Fragments son los siguientes:

– Fijar las callback para interactuar con el Activity durante onAttach():

		//El fragment se ha adjuntado al Activity
		@Override
		public void onAttach(Activity activity) {
			super.onAttach(activity);
			try{
				mCallback = (FragmentIterationListener) activity;
		    }catch(CastClassException ex){
				Log.e("ExampleFragment", "El Activity debe implementar la interfaz FragmentIterationListener");
			}
		}
	

– Instanciar todos los objetos que no sean vistas(Buttons, ListViews, TextViews) en el método onCreate()
– Cargar el layout en el método onCreateView() esto es obligatorio, y se hace de la siguiente manera:

			//El Fragment va a cargar su layout, el cual debemos especificar
			@Override
			public View onCreateView(LayoutInflater inflater, ViewGroup container,
									 Bundle savedInstanceState) {
				super.onCreateView(inflater, container, savedInstanceState);		
				View v =  inflater.inflate(R.layout.examplefragment_view, container, false);
				return v;
			}
	

– Instanciar los objetos que si son vistas, también durante onCreateView():

			//El Fragment va a cargar su layout, el cual debemos especificar
			@Override
			public View onCreateView(LayoutInflater inflater, ViewGroup container,
									 Bundle savedInstanceState) {
				super.onCreateView(inflater, container, savedInstanceState);		
				View v =  inflater.inflate(R.layout.examplefragment_view, container, false);
				if(v != null){
				     textView = (TextView) v.findViewById(R.id.mytextview);
					 listView = (ListView) v.findViewById(R.id.mylistview);
				}
				return v;
			}
	

– Fijar todos los parámetros que queramos de nuestras vistas y restaurar estados en onViewCreated():

			@Override
			public void onViewCreated(View view, Bundle savedInstanceState) {
				super.onViewCreated(view, savedInstanceState);
				listView.setAdapter(new MyListAdapter);
				textView.setText("Hola mundo");
				if(savedInstanceState !=null){
					textView.setText(savedInstanceState.getString("helloWorld");
				}
			}
	

– Fijar los parámetros que tengan que ver con el Activity en onActivityCreated():

		//El Activity que contiene el Fragment ha terminado su creación
		@Override
		public void onActivityCreated(Bundle savedInstanceState) {
			super.onActivityCreated(savedInstanceState);
			setHasOptionsMenu(true); //Indicamos que este Fragment tiene su propio menu de opciones
		}

	

– Eliminar la referencia al Callback durante onDetach():

			//El Fragment ha sido quitado de su Activity y ya no está disponible
		@Override
		public void onDetach() {
			mCallback = null;
			super.onDetach();
		}
	

Si os fijáis hemos incluido una interfaz «FragmentIterationListener», que usamos para comunicarnos con el Activity. En el método onAttach fijamos una referencia al Activity, el cual debe implementar la interfaz FragmentIterationListener, que luego podemos utilizar para pasar objetos del Fragment al Activity para hacer cosas con ellos o pasárselos a otro Fragment.

Así, cuando queramos hacer algo solo tenemos que hacer

	Bundle bundle = new Bundle();
	bundle.putString("datos", "datos que necesito");
	mCallback.onFragmentIeration(bundle);

Podría estar horas escribiendo sobre los Fragments, pero de momento lo dejaremos aquí y ya os contaré más en el futuro. Y vosotros, ¿qué otros consejos útiles conocéis para trabajar con Fragments?