Activities/Intents en Android LSUB, GSYC, URJC
Activity Una unidad de ejecución Para organizar una pantalla Ejecuta no mucho tiempo Y se comunica con otras Arranca otras
Intent Una operación que queremos hacer Referencia una Activity Directamente Con una URI
Bundles Un Bundle es un conjunto de claves:valor Java: (Activity, original) Intent activityintent = new Intent(this, NewActivity.class); Bundle newactivityinfo = new Bundle(); newactivityinfo.putblah( ); // putdouble, putstring, etc. activityintent.putextras(newactivityinfo); startactivity(activityintent); Java: (Activity, nueva) Intent intent = getintent(); Bundle info = intent.getextras(); if (info!= null) { /* Sacar valores con info.getblah(...) */ }
Opción 1: Meter el bundle entero Bundle newactivityinfo = new Bundle(); newactivityinfo.putdouble("clave1", 23.34); newactivityinfo.putstring("clave2", "valor"); miintent.putextras(newactivityinfo);
Opción II: Datos de uno en uno putextra está sobrecargado miintent.putextra("clave1", 34.64); miintent.putextra("clave2", "valor");
Ejemplo Una pantalla de login que arranca otra
AndroidManifest.xml <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" > <activity android:name="org.lsub.app11.mainactivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> Muestra datos, <activity no es la actividad inicial android:name="org.lsub.app11.welcomeactivity" android:label="@string/welcome_name" > <intent-filter> <action android:name="android.intent.action.view" /> <category android:name="android.intent.category.default" /> </intent-filter> </activity> </application>
activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/login_label" /> <EditText android:id="@+id/login_field" android:layout_height="wrap_content" android:layout_weight="1" android:hint="@string/login_hint" android:layout_width="0dp"/> </LinearLayout>
activity_main.xml <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/login_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/login_button" /> </LinearLayout> </LinearLayout>
activity_welcome.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/login_field" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/login_hint" android:inputtype="textnosuggestions" android:maxlines="1" android:singleline="true" /> <TextView android:id="@+id/welcome_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/welcome" /> </LinearLayout>
strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">login</string> <string name="welcome_name">welcome</string> <string name="menu_settings">settings</string> <string name="login_hint">type here</string> <string name="login_label">login:</string> <string name="login_button">press to log in</string> <string name="welcome">welcome! Mr</string> </resources>
MainActivity.java public class MainActivity extends Activity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); Button b = (Button)findViewById(R.id.login_button); b.setonclicklistener(new View.OnClickListener(){ public void onclick(view v){ Intent welcome = new Intent(MainActivity.this, WelcomeActivity.class); Bundle msg = new Bundle(); EditText fld = (EditText)findViewById(R.id.login_field); msg.putstring("usrname", fld.gettext().tostring()); // podiamos usar welcome.putextra("usrname", "blah") welcome.putextras(msg); startactivity(welcome); } }); }
StartActivity Sobrecargado Se le puede pasar otro parámetro que es un bundle, para construirlo: ActivityOptions Para transiciones y otras cosas
Intent Tiene flags para controlar la nueva activity (setflags) Controla la Back Stack https://developer.android.com/guide/ components/activities/tasks-and-backstack.html
WelcomeActivity.java public class WelcomeActivity extends Activity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_welcome); Intent i = getintent(); Bundle msg = i.getextras(); if(msg!= null){ String str = msg.getstring("usrname"); if(str!= null){ TextView lbl = (TextView)findViewById(R.id.welcome_label); lbl.settext("welcome " + str); } } }
Resultado
Resultado
Resultado
Otra opción Registramos una URI Invocamos a la Activity que la sirva
AndroidManifest.xml <activity android:name="org.lsub.app12.welcomeactivity" android:label="@string/welcome_name" > <intent-filter> <action android:name="android.intent.action.view" /> <category android:name="android.intent.category.default" /> <data android:scheme="welcome" android:host="lsub.org" /> </intent-filter> </activity>
Intent usando URI @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); Button b = (Button)findViewById(R.id.login_button); b.setonclicklistener(new View.OnClickListener(){ public void onclick(view v){ Uri u = Uri.parse("welcome://lsub.org/user"); Intent welcome = new Intent(Intent.ACTION_VIEW, u); Bundle msg = new Bundle(); EditText fld = (EditText)findViewById(R.id.login_field); msg.putstring("usrname", fld.gettext().tostring()); welcome.putextras(msg); startactivity(welcome); } }; }
Intent usando URI Y podemos usar también welcome://lsub.org/log?usrname=fjbc Y luego... Uri u = Uri.getIntent().getData(); uname = u.getqueryparameter("usrname");
Estado Hasta un cambio de orientación rearranca el Activity. Cargarlo usando Bundles como en los ejemplos Salvarlo usando este callback: public void onsaveinstancestate(bundle state){ super.onsaveinstancestate(state); state.putstring("usrname", "fjbc"); //... }
Fragments Fragmentos de una Activity Pensados para componerlos https://developer.android.com/guide/ components/fragments.html
Fragments AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.lsub.app13" android:versioncode="1" android:versionname="1.0" > <application android:allowbackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/apptheme" > <activity android:name="org.lsub.app13.mainactivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest>
Fragments res/layout/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity"> <fragment android:name="org.lsub.app13.holafragment" android:id="@+id/hola_fragment" android:layout_height="0dip" android:layout_weight="1" android:layout_width="match_parent" /> <fragment android:name="org.lsub.app13.caracolafragment" android:id="@+id/caracola_fragment" android:layout_height="0dip" android:layout_weight="1" android:layout_width="match_parent" /> </LinearLayout>
Fragments res/layout/hola_fragment.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:text="hola!" /> </LinearLayout>
Fragments res/layout/caracola_fragment.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:text="caracola" /> </LinearLayout>
Fragments MainActivity.java Ya no es necesario public class MainActivity extends FragmentActivity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); } } @Override public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.activity_main, menu); return true; }
Fragments HolaFragment.java public class HolaFragment extends Fragment { @Override public View oncreateview(layoutinflater inf, ViewGroup view, Bundle savedinstancestate) { return inf.inflate(r.layout.hola_fragment, view, false); } }
Fragments CaracolaFragment.java public class CaracolaFragment extends Fragment { @Override public View oncreateview(layoutinflater inf, ViewGroup view, Bundle savedinstancestate) { return inf.inflate(r.layout.caracola_fragment, view, false); } }
Resultado
Pasando de uno a otro Cambiemos de uno a otro En tiempo de ejecución Todo como antes, salvo...
Fragments res/layout/activity_main.xml <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_frame" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".mainactivity"> </FrameLayout>
Fragments res/layout/hola_fragment.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:text="hola!" android:onclick="clicked" /> </LinearLayout>
Fragments MainActivity.java public class MainActivity extends FragmentActivity { @Override protected void oncreate(bundle state) { super.oncreate(state); setcontentview(r.layout.activity_main); if(state!= null) return; HolaFragment hola = new HolaFragment(); hola.setarguments(getintent().getextras()); FragmentManager mgr = getsupportfragmentmanager(); FragmentTransaction t = mgr.begintransaction(); t.add(r.id.main_frame, hola); t.commit(); }
Fragments MainActivity.java public void clicked(view v){ CaracolaFragment car = new CaracolaFragment(); car.setarguments(getintent().getextras()); FragmentManager mgr = getsupportfragmentmanager(); FragmentTransaction t = mgr.begintransaction(); t.replace(r.id.main_frame, car); t.addtobackstack(null); t.commit(); }
Resultado Y tras click...
Resultado
Fragmentos En realidad tenemos una actividad Mira por ej. onclick... Combinamos fragmentos a la vez o Uno tras otro
Callbacks en Fragments oncreate() Cuando realmente se crea oncreateview() Cuando se muestra
Callbacks en Fragments onattach(activity) Justo antes de crearse
Callbacks en Fragments Por lo demás como en las Activities
Sugerencia Redefine los callbacks de un Fragment Pon un write en cada uno Arranca el emulador y ejecuta Entrando y Saliendo Cambiando la orientación (Ctrl-F12)
Fragments en lata Hay unos cuantos útiles predefinidos DialogFragment: diálogo flotante ListFragment: lista de items PreferenceFragment: preferencias
Fragments en lata Creo un fragmento nuevo (Android Studio) Lo hago heredar del enlatado Miro el tutorial, por ejemplo: https://developer.android.com/reference/ android/app/dialogfragment.html
Recursos, layouts,... Recordatorio sobre res/... Definimos los por defecto en los directorios estándar Definimos otros específicos para UK, ES, landscape, portrait,...
Recursos, layouts,... Localización...-en,...-es,...-enrUK,... Pantalla...-large,...-normal,...-large,...-xlarge Orientación...-port,...-land Resolución xhdpi, hdpi, mdpi, ldpi
Recursos, layouts,... Android carga primero los no específicos Y sobre ellos los específicos que apliquen Basta redefinir justo lo que queramos Y el resto queda como esté definido en los no específicos.
Recursos, layouts,... Precedencia: 1. Localización (lenguaje) 2. Tamaño de pantalla 3. Orientación 4. Resolución
Recursos, layouts,... Ejemplo: res/drawable, res/drawable-ldpi res/layout, res/layout-land res/values, res/values-es
Tests Dos tipos, junit (con android.jar vacío, hay que hacer mocking o abstraer) Instrumentados (androidtest) ejecutan en el teléfono o emulador https://developer.android.com/training/ testing/start/index.html
Tests instrumentados Instrumentados, ojo, hay que anotar build.grade (Module: app) dependencies { androidtestcompile 'com.android.support:support-annotations:24.0.0' androidtestcompile 'com.android.support.test:runner:0.5' OJO androidtestcompile 'com.android.support.test:rules:0.5' // Optional -- Hamcrest library androidtestcompile 'org.hamcrest:hamcrest-library:1.3' // Optional -- UI testing with Espresso androidtestcompile 'com.android.support.test.espresso:espresso-core:2.2.2' // Optional -- UI testing with UI Automator androidtestcompile com.android.support.test.uiautomator:uiautomator-v18:2.1.2 }
Tests instrumentados Si la versión de la página anterior no está bien error al compilar el test (botón derecho run sobre el test): Resolved versions for app (25.1.0) and test app (24.0.0) differ
Tests instrumentados: UI Ejecutar la aplicación Tocar el UI Anotaciones (deprecated) Espresso es la manera ahora
Tests instrumentados: setup espresso Anotar que anotar build.grade (Module: app) dependencies { androidtestcompile 'com.android.support:support-annotations:24.0.0' androidtestcompile 'com.android.support.test:runner:0.5' androidtestcompile 'com.android.support.test:rules:0.5' // Optional -- Hamcrest library androidtestcompile 'org.hamcrest:hamcrest-library:1.3' // Optional -- UI testing with Espresso androidtestcompile 'com.android.support.test.espresso:espresso-core:2.2.2' // Optional -- UI testing with UI Automator androidtestcompile com.android.support.test.uiautomator:uiautomator-v18:2.1.2 }
Test instrumentados Añadir imports estáticos para espresso package org.lsub.paurea.pruebaclase; import android.content.context; import android.support.test.instrumentationregistry; import android.support.test.rule.activitytestrule; import android.support.test.runner.androidjunit4; import android.widget.button; import static android.support.test.espresso.espresso.onview; import static android.support.test.espresso.action.viewactions.click; import static android.support.test.espresso.assertion.viewassertions.matches; import static android.support.test.espresso.matcher.viewmatchers.isdisplayed; import static android.support.test.espresso.matcher.viewmatchers.withid; import org.junit.rule; import org.junit.test; import org.junit.runner.runwith; import static org.junit.assert.*;
Test instrumentados Puedo arrancar la Activity y apretar un botón @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Rule public ActivityTestRule<MainActivity> mactivityrule = new ActivityTestRule<>(MainActivity.class); @Test public void changetext_sameactivity() { // Type text and then press the button. onview(withid(r.id.button)).perform(click()); } }
Test instrumentados Meter texto, apretar un botón // Escribo texto con el teclado onview(withid(r.id.edittextuserinput)).perform(typetext(string_to_be_typed), closesoftkeyboard()); //Aprieto el bot. onview(withid(r.id.changetextbt)).perform(click()); //Compruebo que ha cambiado onview(withid(r.id.texttobechanged)).check(matches(withtext(mstringtobetyped)));
Test instrumentados Llamar a un método de la activity public ActivityTestRule<MainActivity> mactivityrule = new ActivityTestRule<MainActivity>(MainActivity.class); @Test public void useappcontext() throws Exception { MainActivity activity = mactivityrule.getactivity(); //llamo a un metodo de la activity activity.mymethod("whatever"); // do more }
Test instrumentados Más info: https://developer.android.com/topic/libraries/testing-support-library/index.html Aparte está el UI automator (para interactuar con menús del sistema y demás)