Chuleta Android · Activities, controles y menús

Todo lo que sale en tus 9 prácticas, ordenado por concepto y con la ficha de cada ejercicio para saber cuál abrir en el examen práctico.

KotlinConstraintLayoutIntentsListView · SpinnerToolbar · MenúsAlertDialog · Toast

🗺️Mapa de las prácticas

9 apps, un mismo esqueleto. Esta tabla te dice en qué práctica buscar cada cosa cuando estés en el examen.

ConceptoDónde está
Abrir otra Activity (Intent explícito)Ex1 Author
Cerrar pantalla con finish()Ex1Menús
Pasar datos con putExtra / getStringExtraEx2 PasswordEx3 LOTR
Mensaje de error en un EditText (.error)Ex2Ex3
Mostrar / ocultar vistas (visibility)Ex2Ex3
AlertDialog de confirmaciónEx2Ex3Ex4
Escuchar lo que se escribe (doOnTextChanged)Ex3
Arrays paralelos + indexOfFirstEx3
Interceptar botón Atrás (OnBackPressedCallback)Ex3
Fuentes personalizadas (res/font)Ex3Ex4
Devolver datos (registerForActivityResult + setResult)Ex4 Name or age
Cambiar inputType desde códigoEx4
RadioGroup / RadioButtonP3.1.a Suma o resta
CheckBox + setOnCheckedChangeListenerP3.1.b Suma i resta
Toast, toDoubleOrNull(), ScrollViewP3.1.aP3.1.b
ListView + ArrayAdapterP3.2 ListView
Spinner + OnItemSelectedListenerP3.3 Spinner
Toolbar + menú de opciones (res/menu)Pt1.2 Menús
Intent implícito (abrir una web)Pt1.2 Menús
Strings multiidioma (values-en, values-fr)Pt1.2 Menús
Temas / colores (themes.xml)Pt1.2 Menús
Edge-to-edge + WindowInsetsEx2Ex3

📱Anatomía de una Activity

Una Activity = una pantalla. Siempre tiene 3 piezas: la clase Kotlin, el layout XML y su registro en el Manifest.

1MainActivity.ktapp/java/cat.institutmarianao.xxx/
2activity_main.xmlapp/res/layout/
3AndroidManifest.xmlapp/manifests/

El esqueleto que usas en todas las prácticas

kotlinPatrón initViews() + setupListeners() (Ex1, Ex2, Ex3)
class MainActivity : AppCompatActivity() {

    // 1. Referencias a los componentes del XML (se rellenan en onCreate)
    private lateinit var etUsername: EditText
    private lateinit var btnCheck: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)   // qué XML pinta esta pantalla

        initViews()
        setupListeners()
    }

    // 2. Buscar los componentes por su id
    private fun initViews() {
        etUsername = findViewById(R.id.editTextText2)
        btnCheck = findViewById(R.id.button)
    }

    // 3. Qué pasa al pulsar
    private fun setupListeners() {
        btnCheck.setOnClickListener { checkLogin() }
    }
}
lateinit var"la inicializo más tarde" (en onCreate). Si la usas antes → UninitializedPropertyAccessException.
setContentView(R.layout.x)asocia el XML. Siempre antes de los findViewById, si no devuelven null y peta.
findViewById<Button>(R.id.x)dos formas: con tipo en la variable (val b: Button = findViewById(...)) o con genérico findViewById<Button>(...).
val local vs lateinit varEn P3.1.a se usan val dentro de onCreate (más corto). Si otra función necesita la vista → lateinit var de clase.

Registrar la Activity en el Manifest

Si creas la Activity con File ▸ New ▸ Activity ▸ Empty Views Activity, Android Studio la añade solo. Si la creas a mano, añádela tú o tendrás ActivityNotFoundException.

xmlAndroidManifest.xml
<application ... android:theme="@style/Theme.Author">
    <!-- Pantalla secundaria: no se abre desde fuera de la app -->
    <activity
        android:name=".AuthorActivity"
        android:exported="false" />

    <!-- Pantalla de inicio: MAIN + LAUNCHER = icono en el móvil -->
    <activity
        android:name=".MainActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

Ciclo de vida (lo básico)

onCreate→onStart→onResume→▶ en uso→onPause→onStop→onDestroy

Al girar la pantalla la Activity se destruye y se recrea. Por eso en Ex4 y P3.1 los TextView de resultado llevan android:freezesText="true": así conservan el texto al rotar.

🎯 Para el examen
Las EditText guardan su texto al rotar solas (si tienen id). Los TextView NO, a menos que pongas freezesText="true".

📐Layouts: ConstraintLayout

Todas las prácticas usan ConstraintLayout: cada vista se "ata" con constraints a los bordes del padre o a otras vistas.

Las constraints que más usas

AtributoSignifica
app:layout_constraintTop_toTopOf="parent"mi arriba pegado al arriba del padre
app:layout_constraintTop_toBottomOf="@id/x"me pongo debajo de x
app:layout_constraintBottom_toTopOf="@id/x"me pongo encima de x
Start_toStartOf="parent" + End_toEndOf="parent"centrado horizontal
Top_toTopOf="parent" + Bottom_toBottomOf="parent"centrado vertical
Start_toEndOf="@id/x"me pongo a la derecha de x (Ex4: Cancel al lado de Submit)
Top_toTopOf="@id/x"alineado arriba con x (misma fila)
app:layout_constraintVertical_chainStyle="packed"cadena vertical: el grupo de vistas va junto y centrado
app:layout_constraintHorizontal_bias="0.5"0 = izquierda, 1 = derecha, 0.5 = centro

Cadena vertical centrada (Ex1, Ex2, Ex3, Menús)

Para que 3 vistas queden juntas en el centro de la pantalla: la primera se ata al parent arriba, la última al parent abajo, y entre ellas se atan mutuamente. En la primera pones chainStyle="packed".

xmlactivity_info.xml (Ex1)
<TextView
    android:id="@+id/tvSubtitle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="This is an app developed by"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toTopOf="@+id/tvAuthorName"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintVertical_chainStyle="packed" />

<TextView
    android:id="@+id/tvAuthorName"
    ...
    android:layout_marginTop="12dp"
    app:layout_constraintTop_toBottomOf="@+id/tvSubtitle"
    app:layout_constraintBottom_toTopOf="@+id/btnClose" />

<Button
    android:id="@+id/btnClose"
    ...
    app:layout_constraintTop_toBottomOf="@+id/tvAuthorName"
    app:layout_constraintBottom_toBottomOf="parent" />

Tamaños

wrap_contentlo justo para el contenido
match_parenttodo el ancho/alto del padre
0dp (match constraint)ocupa el espacio entre sus constraints. Lo usas en ListView/EditText que van de borde a borde.
dp vs spdp para tamaños y márgenes, sp para texto (respeta el tamaño de letra del usuario).

ScrollView (P3.1.a / P3.1.b)

Para que el formulario se pueda desplazar si no cabe (p. ej. en horizontal). Solo puede tener UN hijo → dentro va el ConstraintLayout con todo.

xmlactivity_main.xml (P3.1)
<ScrollView
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="32dp">
        <!-- EditText, RadioGroup, Button... -->
    </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
⚠️ Ojo
Dentro de un ScrollView, el hijo va con layout_height="wrap_content", no match_parent.

Atributos útiles que salen en las prácticas

AtributoPara quéDónde
android:id="@+id/x"@+id crea el id; @id lo referenciatodas
android:hint="Username"texto gris de ayuda en un EditTextEx2, Ex3
android:inputTypetext, textPassword, number, numberDecimal|numberSignedEx2, P3.1
android:ems="10"ancho de ~10 letras "M"Ex2, Ex3, Ex4
android:gravity="end"alinea el contenido (texto) dentro de la vistaP3.1
android:visibility="gone"oculta desde el principioEx2 (tvError), Ex3 (botón)
tools:text="..."texto solo en el diseñador, no en la appEx2, Ex3
android:fontFamily="@font/aclonica"fuente descargable (Google Fonts)Ex3, Ex4
android:textColor="?attr/colorPrimary"color del tema actualMenús
android:freezesText="true"conserva el texto del TextView al rotarEx4, P3.1
💡 Truco
Fuente personalizada: selecciona el TextView ▸ atributo fontFamily ▸ More Fonts… ▸ elige ▸ Create downloadable font. Android Studio crea res/font/xxx.xml, font_certs.xml, preloaded_fonts.xml y el meta-data del Manifest.

🧱Plantillas de layouts

Tres o cuatro "esqueletos" que copias, pegas y borras lo que sobra. Todos tienen la Toolbar con el menú ⋮ ya integrada y los mismos ids, así que el Kotlin te vale para cualquiera.

El truco de todas: el esqueleto

LinearLayout vertical · match_parent · id=main
Toolbar · alto ?attr/actionBarSize → fija arriba, con el menú ⋮
ScrollView · height=0dp + weight=1 → "todo lo que queda"
LinearLayout vertical o ConstraintLayout → UN solo hijo
TextView, EditText, Spinner, RadioGroup, CheckBox, Button…
💡 Truco
¿Por qué el root es un LinearLayout y no un ConstraintLayout? Porque así la Toolbar queda arriba y el ScrollView ocupa el resto con weight, sin una sola constraint. La Toolbar no se mueve al hacer scroll.

¿Cuál uso?

1 · Formulario (Linear)
Mi App🔍 ℹ ⋮
Título
Nombre
Número
Opción A ▾
◉ Opción 1   ○ Opción 2
☑ Check 1   ☐ Check 2
ACEPTAR
LIMPIAR
Resultado
⇕ scroll

plantilla_scroll_linear.xml
La más fácil. Para cualquier pantalla con campos y botones.

2 · Formulario (Constraint)
Mi App🔍 ℹ ⋮
Título
Nombre
Número
Opción A ▾
◉ Opción 1   ○ Opción 2
☑ Check 1   ☐ Check 2
ACEPTAR
LIMPIAR
Resultado
⇕ scroll

plantilla_scroll_constraint.xml
Lo mismo con ConstraintLayout, por si te lo piden.

3 · Pantalla centrada
←Detalle🔍 ℹ ⋮
Bienvenido
Marouan
VOLVER

plantilla_centrada.xml
Welcome / Author / Score: textos + botón en el centro, con flecha ←.

4 · Lista
Lista🔍 ℹ ⋮
Selecciona un elemento
Elemento 1
Elemento 2
Elemento 3
Elemento 4
Elemento 5
Elemento 6

plantilla_lista.xml
ListView ocupando el resto. Sin ScrollView.

LinearLayout en 1 minuto

android:orientationvertical = uno debajo de otro · horizontal = uno al lado de otro. Si no lo pones, es horizontal (error típico: todo sale en una fila).
layout_weight="1"reparte el espacio que sobra. Pon a 0dp la medida en la dirección de la orientación (vertical → height=0dp, horizontal → width=0dp).
gravitycoloca a los hijos dentro de mí (center, end…)
layout_gravityme coloca a mí dentro de mi padre
padding / layout_marginpadding = espacio por dentro · margin = espacio por fuera
AnidarPara poner cosas en fila dentro de un Linear vertical → mete un LinearLayout horizontal dentro (como los 2 botones).
Dos botones mitad y mitadLinearLayout horizontal + cada botón width=0dp y weight=1.
Un botón 2/3 y otro 1/3weight=2 y weight=1.

Plantilla 1 · Toolbar + Scroll + LinearLayout

xmlres/layout/plantilla_scroll_linear.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <!-- ===== TOOLBAR (con el menú ⋮ integrado) ===== -->
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.Material3.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.Material3.Light"
        app:title="Mi App"
        app:titleTextColor="?attr/colorOnPrimary" />

    <!-- ===== ZONA CON SCROLL: height 0dp + weight 1 = "todo lo que queda" ===== -->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fillViewport="true">

        <!-- El ScrollView solo admite UN hijo: este LinearLayout -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">

            <!-- Título -->
            <TextView
                android:id="@+id/tvTitulo"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:gravity="center"
                android:text="Título de la pantalla"
                android:textSize="24sp"
                android:textStyle="bold" />

            <!-- Texto -->
            <EditText
                android:id="@+id/etNombre"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Nombre"
                android:inputType="text" />

            <!-- Número (decimal y con signo) -->
            <EditText
                android:id="@+id/etNumero"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:hint="Número"
                android:inputType="numberDecimal|numberSigned" />

            <!-- Desplegable: las opciones salen de res/values/arrays.xml (sin adapter) -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="Elige una opción:" />

            <Spinner
                android:id="@+id/spOpciones"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:entries="@array/opciones" />

            <!-- Radio: solo una opción -->
            <RadioGroup
                android:id="@+id/rgOpciones"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:checkedButton="@id/rbOpcion1"
                android:orientation="horizontal">

                <RadioButton
                    android:id="@+id/rbOpcion1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Opción 1" />

                <RadioButton
                    android:id="@+id/rbOpcion2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:text="Opción 2" />
            </RadioGroup>

            <!-- Check: varias a la vez (en fila) -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:orientation="horizontal">

                <CheckBox
                    android:id="@+id/cbOpcion1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Check 1" />

                <CheckBox
                    android:id="@+id/cbOpcion2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:text="Check 2" />
            </LinearLayout>

            <!-- Dos botones en la misma fila, mitad y mitad (width 0dp + weight 1) -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:orientation="horizontal">

                <Button
                    android:id="@+id/btnAceptar"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="Aceptar" />

                <Button
                    android:id="@+id/btnLimpiar"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_weight="1"
                    android:text="Limpiar" />
            </LinearLayout>

            <!-- Resultado (freezesText = no se pierde al girar) -->
            <TextView
                android:id="@+id/tvResultado"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:freezesText="true"
                android:gravity="center"
                android:text="Resultado"
                android:textSize="18sp" />

        </LinearLayout>
    </ScrollView>
</LinearLayout>

Plantilla 2 · Toolbar + Scroll + ConstraintLayout

Regla para no liarte: cada vista Top_toBottomOf la anterior + width=0dp con Start/End al parent (ocupa todo el ancho). Para ponerla al lado de otra: Start_toEndOf + Top_toTopOf.

xmlres/layout/plantilla_scroll_constraint.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.Material3.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.Material3.Light"
        app:title="Mi App"
        app:titleTextColor="?attr/colorOnPrimary" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:fillViewport="true">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp">

            <TextView
                android:id="@+id/tvTitulo"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="Título de la pantalla"
                android:textSize="24sp"
                android:textStyle="bold"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <EditText
                android:id="@+id/etNombre"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:hint="Nombre"
                android:inputType="text"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tvTitulo" />

            <EditText
                android:id="@+id/etNumero"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:hint="Número"
                android:inputType="numberDecimal|numberSigned"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/etNombre" />

            <Spinner
                android:id="@+id/spOpciones"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:entries="@array/opciones"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/etNumero" />

            <!-- Dentro del RadioGroup NO hay constraints: el grupo es un LinearLayout -->
            <RadioGroup
                android:id="@+id/rgOpciones"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:checkedButton="@id/rbOpcion1"
                android:orientation="horizontal"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/spOpciones">

                <RadioButton
                    android:id="@+id/rbOpcion1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Opción 1" />

                <RadioButton
                    android:id="@+id/rbOpcion2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:text="Opción 2" />
            </RadioGroup>

            <!-- Dos CheckBox en la misma fila -->
            <CheckBox
                android:id="@+id/cbOpcion1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="Check 1"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/rgOpciones" />

            <CheckBox
                android:id="@+id/cbOpcion2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:text="Check 2"
                app:layout_constraintStart_toEndOf="@id/cbOpcion1"
                app:layout_constraintTop_toTopOf="@id/cbOpcion1" />

            <!-- Dos botones mitad y mitad: cadena horizontal con width 0dp -->
            <Button
                android:id="@+id/btnAceptar"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="4dp"
                android:text="Aceptar"
                app:layout_constraintEnd_toStartOf="@id/btnLimpiar"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/cbOpcion1" />

            <Button
                android:id="@+id/btnLimpiar"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="4dp"
                android:text="Limpiar"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/btnAceptar"
                app:layout_constraintTop_toTopOf="@id/btnAceptar" />

            <TextView
                android:id="@+id/tvResultado"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:freezesText="true"
                android:gravity="center"
                android:text="Resultado"
                android:textSize="18sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/btnAceptar" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </ScrollView>
</LinearLayout>

Plantilla 3 · Pantalla centrada (segunda Activity)

xmlres/layout/plantilla_centrada.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SegundaActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.Material3.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.Material3.Light"
        app:title="Detalle"
        app:titleTextColor="?attr/colorOnPrimary" />

    <!-- gravity="center" centra a los HIJOS en vertical y horizontal -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="24dp">

        <TextView
            android:id="@+id/tvLinea1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Bienvenido"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/tvLinea2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:freezesText="true"
            android:textSize="26sp"
            android:textStyle="bold"
            tools:text="Marouan" />

        <Button
            android:id="@+id/btnVolver"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="Volver" />
    </LinearLayout>
</LinearLayout>

Plantilla 4 · Lista

xmlres/layout/plantilla_lista.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp"
        android:theme="@style/ThemeOverlay.Material3.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.Material3.Light"
        app:title="Lista"
        app:titleTextColor="?attr/colorOnPrimary" />

    <TextView
        android:id="@+id/tvInfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:freezesText="true"
        android:padding="16dp"
        android:text="Selecciona un elemento"
        android:textSize="18sp" />

    <!-- height 0dp + weight 1 = ocupa el resto de la pantalla -->
    <ListView
        android:id="@+id/lvLista"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>
⚠️ Ojo
Nunca metas un ListView dentro de un ScrollView: el ListView ya tiene scroll y solo se vería una fila.

Opciones del Spinner sin adapter

Con android:entries="@array/opciones" el Spinner se rellena solo. Crea res/values/arrays.xml:

xmlres/values/arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- res/values/arrays.xml · opciones del Spinner (android:entries="@array/opciones") -->
<resources>
    <string-array name="opciones">
        <item>Opción A</item>
        <item>Opción B</item>
        <item>Opción C</item>
    </string-array>
</resources>
kotlinLeer el Spinner
val texto = spOpciones.selectedItem.toString()   // "Opción B"
val pos   = spOpciones.selectedItemPosition      // 1
spOpciones.setSelection(0)                       // volver a la primera

Menú genérico (iconos + ⋮ + submenú)

Los iconos @android:drawable/ic_menu_search, ic_menu_info_details, ic_menu_add, ic_menu_delete, ic_menu_edit, ic_menu_share… ya vienen con Android: no tienes que crear Vector Assets.

xmlres/menu/menu_main.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- ===== Iconos en la Toolbar ===== -->
    <item
        android:id="@+id/action_buscar"
        android:icon="@android:drawable/ic_menu_search"
        android:title="Buscar"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/action_info"
        android:icon="@android:drawable/ic_menu_info_details"
        android:title="Información"
        app:showAsAction="ifRoom" />

    <!-- ===== Dentro del ⋮ ===== -->
    <item
        android:id="@+id/action_pantalla2"
        android:title="Ir a pantalla 2"
        app:showAsAction="never" />

    <item
        android:id="@+id/action_web"
        android:title="Abrir web"
        app:showAsAction="never" />

    <!-- Submenú: al pulsar "Más opciones" se abre otro desplegable -->
    <item
        android:id="@+id/action_mas"
        android:title="Más opciones"
        app:showAsAction="never">
        <menu>
            <item
                android:id="@+id/action_sub1"
                android:title="Subopción 1" />
            <item
                android:id="@+id/action_sub2"
                android:title="Subopción 2" />
        </menu>
    </item>

    <item
        android:id="@+id/action_salir"
        android:title="Salir"
        app:showAsAction="never" />
</menu>

Kotlin de la plantilla 1 / 2 (Toolbar + menú + leer todos los controles)

kotlinMainActivity.kt
package cat.institutmarianao.examen   // ← cambia por el package de TU proyecto

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

/**
 * PLANTILLA de Activity para plantilla_scroll_linear.xml o plantilla_scroll_constraint.xml
 * (las dos tienen los mismos ids). Borra lo que no uses.
 */
class MainActivity : AppCompatActivity() {

    companion object {
        const val EXTRA_NOMBRE = "EXTRA_NOMBRE"
    }

    private lateinit var etNombre: EditText
    private lateinit var etNumero: EditText
    private lateinit var spOpciones: Spinner
    private lateinit var rgOpciones: RadioGroup
    private lateinit var rbOpcion1: RadioButton
    private lateinit var cbOpcion1: CheckBox
    private lateinit var cbOpcion2: CheckBox
    private lateinit var btnAceptar: Button
    private lateinit var btnLimpiar: Button
    private lateinit var tvResultado: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.plantilla_scroll_linear)   // ← nombre de tu XML

        setupInsets()
        setupToolbar()
        initViews()
        setupListeners()
    }

    // Que la Toolbar no quede debajo de la barra de estado (necesita android:id="@+id/main")
    private fun setupInsets() {
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
            insets
        }
    }

    // La Toolbar del XML pasa a ser la barra de la app (el tema debe ser NoActionBar)
    private fun setupToolbar() {
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
        supportActionBar?.title = "Mi App"
    }

    private fun initViews() {
        etNombre = findViewById(R.id.etNombre)
        etNumero = findViewById(R.id.etNumero)
        spOpciones = findViewById(R.id.spOpciones)
        rgOpciones = findViewById(R.id.rgOpciones)
        rbOpcion1 = findViewById(R.id.rbOpcion1)
        cbOpcion1 = findViewById(R.id.cbOpcion1)
        cbOpcion2 = findViewById(R.id.cbOpcion2)
        btnAceptar = findViewById(R.id.btnAceptar)
        btnLimpiar = findViewById(R.id.btnLimpiar)
        tvResultado = findViewById(R.id.tvResultado)
    }

    private fun setupListeners() {
        btnAceptar.setOnClickListener { aceptar() }
        btnLimpiar.setOnClickListener { limpiar() }

        // (Opcional) reaccionar al cambiar el Spinner
        spOpciones.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
                // position = 0, 1, 2...  |  parent?.getItemAtPosition(position) = el texto
            }
            override fun onNothingSelected(parent: AdapterView<*>?) {}
        }

        // (Opcional) reaccionar al marcar un CheckBox
        cbOpcion1.setOnCheckedChangeListener { _, marcado -> /* marcado = true/false */ }
    }

    private fun aceptar() {
        // ---- Leer EditText ----
        val nombre = etNombre.text.toString().trim()
        if (nombre.isEmpty()) {
            etNombre.error = "Campo obligatorio"
            return
        }
        val numero = etNumero.text.toString().toDoubleOrNull()   // null si está vacío o mal

        // ---- Leer Spinner ----
        val opcionSpinner = spOpciones.selectedItem.toString()
        val posSpinner = spOpciones.selectedItemPosition

        // ---- Leer RadioGroup ----
        val radioMarcado = findViewById<RadioButton>(rgOpciones.checkedRadioButtonId).text
        // o: if (rbOpcion1.isChecked) ... else ...

        // ---- Leer CheckBox ----
        val checks = mutableListOf<String>()
        if (cbOpcion1.isChecked) checks.add("Check 1")
        if (cbOpcion2.isChecked) checks.add("Check 2")

        tvResultado.text = "Nombre: $nombre\nNúmero: ${numero ?: "-"}\n" +
                "Spinner: $opcionSpinner ($posSpinner)\nRadio: $radioMarcado\n" +
                "Checks: ${checks.joinToString()}"
    }

    private fun limpiar() {
        etNombre.text.clear()
        etNumero.text.clear()
        spOpciones.setSelection(0)
        rbOpcion1.isChecked = true
        cbOpcion1.isChecked = false
        cbOpcion2.isChecked = false
        tvResultado.text = "Resultado"
    }

    // ================== MENÚ DE LA TOOLBAR ==================

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.action_pantalla2 -> {
                val intent = Intent(this, SegundaActivity::class.java)
                intent.putExtra(EXTRA_NOMBRE, etNombre.text.toString())
                startActivity(intent)
            }
            R.id.action_web -> {
                startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com")))
            }
            R.id.action_mas -> { /* abre el submenú solo, no hay que hacer nada */ }
            R.id.action_salir -> confirmarSalir()
            else -> Toast.makeText(this, item.title.toString(), Toast.LENGTH_SHORT).show()
        }
        return true
    }

    private fun confirmarSalir() {
        AlertDialog.Builder(this)
            .setTitle("Salir")
            .setMessage("¿Seguro que quieres salir?")
            .setPositiveButton("Sí") { _, _ -> finish() }
            .setNegativeButton("No", null)
            .show()
    }
}

Kotlin de la plantilla 3 (con flecha ← en la Toolbar)

kotlinSegundaActivity.kt
package cat.institutmarianao.examen   // ← cambia por el package de TU proyecto

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

/**
 * PLANTILLA de segunda pantalla para plantilla_centrada.xml
 * - Toolbar con flecha ← para volver
 * - Recibe un dato por Intent
 * Recuerda: debe estar en el AndroidManifest (si la creas con New ▸ Activity se añade sola).
 */
class SegundaActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.plantilla_centrada)

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
            insets
        }

        // Toolbar con flecha de volver
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
        supportActionBar?.title = "Detalle"
        supportActionBar?.setDisplayHomeAsUpEnabled(true)   // muestra la flecha ←
        toolbar.setNavigationOnClickListener { finish() }   // qué hace la flecha

        // Dato recibido
        val nombre = intent.getStringExtra(MainActivity.EXTRA_NOMBRE) ?: "Usuario"
        findViewById<TextView>(R.id.tvLinea2).text = nombre

        findViewById<Button>(R.id.btnVolver).setOnClickListener { finish() }
    }
}

Kotlin de la plantilla 4

kotlinListaActivity.kt
package cat.institutmarianao.examen   // ← cambia por el package de TU proyecto

import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar

/** PLANTILLA para plantilla_lista.xml (Toolbar + ListView) */
class ListaActivity : AppCompatActivity() {

    private val elementos = listOf("Elemento 1", "Elemento 2", "Elemento 3", "Elemento 4")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.plantilla_lista)

        setSupportActionBar(findViewById<Toolbar>(R.id.toolbar))

        val tvInfo = findViewById<TextView>(R.id.tvInfo)
        val lvLista = findViewById<ListView>(R.id.lvLista)

        lvLista.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, elementos)

        lvLista.setOnItemClickListener { _, _, position, _ ->
            tvInfo.text = "Has pulsado: ${elementos[position]}"
        }
    }
}

Cómo meterlas en tu proyecto del examen

  1. Crea la Activity con File ▸ New ▸ Activity ▸ Empty Views Activity (así se añade al Manifest).
  2. Abre su XML (res/layout/activity_xxx.xml), pasa a la vista Code, borra todo y pega la plantilla.
  3. Cambia tools:context=".MainActivity" por el nombre de tu Activity (solo afecta a la vista previa).
  4. Si usas el Spinner con @array/opciones: crea res/values/arrays.xml.
  5. Si usas menú: crea res/menu/menu_main.xml (clic derecho en res ▸ New ▸ Android Resource File ▸ tipo Menu).
  6. En Kotlin pega solo los métodos que necesites, sin tocar la línea package de tu fichero. Revisa que setContentView(R.layout.…) apunta a tu XML.
  7. Comprueba que el tema en res/values/themes.xml es ...NoActionBar (en los proyectos nuevos ya lo es).

Atajo: menú sin poner Toolbar

Si no quieres Toolbar en el XML, cambia el padre del tema de Theme.Material3.DayNight.NoActionBar a Theme.Material3.DayNight. Entonces la app trae su barra de serie y onCreateOptionsMenu funciona igual, sin setSupportActionBar.

xmlTema CON barra
<!-- res/values/themes.xml -->
<style name="Base.Theme.MiApp" parent="Theme.Material3.DayNight">
🎯 Para el examen
Con el tema NoActionBar y sin Toolbar, supportActionBar es null: supportActionBar?.title = ... no hace nada y el menú no aparece. Con la Toolbar y el tema con barra → error "This Activity already has an action bar". Elige una de las dos opciones.

Errores típicos de layouts

Todo sale en una filaFalta android:orientation="vertical" en el LinearLayout.
"ScrollView can host only one direct child"Has metido 2 vistas en el ScrollView → envuélvelas en un LinearLayout.
El scroll no deja ver lo de abajo / tapa la ToolbarEl ScrollView debe ir con height=0dp + weight=1 (o match_parent si no hay Toolbar).
El weight no reparte bienTe falta poner a 0dp el ancho (horizontal) o el alto (vertical).
En Constraint todo sale arriba a la izquierdaA la vista le faltan constraints (sale el aviso rojo "not constrained").
La Toolbar queda debajo de la hora/bateríaFalta el bloque de WindowInsets de enableEdgeToEdge, o el root no tiene android:id="@+id/main".
Iconos del menú en negro sobre azulFalta android:theme="@style/ThemeOverlay.Material3.Dark.ActionBar" en la Toolbar.
Crash al leer el RadioGroupcheckedRadioButtonId es -1 si no hay ninguno marcado → pon android:checkedButton.

🎛️Controles básicos

TextView · EditText · Button

kotlinEditText / TextView
// Leer lo que ha escrito el usuario
val username = etUsername.text.toString().trim()   // trim() quita espacios
val password = etPassword.text.toString()

// Validar vacío y mostrar error dentro del propio EditText
if (username.isEmpty()) {
    etUsername.error = "Introduce un nombre de usuario !!!"
    return
}

// Escribir en un TextView
tvWelcome.text = "Welcome $username"        // plantilla de String con $

// Vaciar un EditText
etNumber1.text.clear()

// Cambiar el texto de un botón
btnCalculate.text = "Add & Subtract"

Convertir texto a número sin que pete

kotlinP3.1.a / P3.1.b
val num1 = etNumber1.text.toString().toDoubleOrNull()   // null si no es número
val num2 = etNumber2.text.toString().toDoubleOrNull()

if (num1 == null || num2 == null) {
    Toast.makeText(this, "Introduce los dos números", Toast.LENGTH_SHORT).show()
    return@setOnClickListener      // salir de la lambda (o return si es una función)
}
⚠️ Ojo
toDouble() lanza excepción con texto vacío. Usa toDoubleOrNull() / toIntOrNull(). Dentro de un setOnClickListener { } se sale con return@setOnClickListener.

Visibility

View.VISIBLEse ve
View.INVISIBLEno se ve, pero ocupa su hueco
View.GONEno se ve y no ocupa espacio
kotlinEx2 Password
tvError.text = "Wrong password.\n It's so easy as abc123"
tvError.visibility = View.VISIBLE   // mostrar
tvError.visibility = View.GONE      // ocultar

Escuchar mientras se escribe — doOnTextChanged (Ex3)

kotlinEl botón solo aparece cuando hay texto
// import androidx.core.widget.doOnTextChanged
etUsername.doOnTextChanged { text, _, _, _ ->
    if (text != null && text.length >= 1) {
        btnCheck.visibility = View.VISIBLE
    } else {
        btnCheck.visibility = View.GONE
    }
}

Cambiar el teclado desde código (Ex4)

kotlinDataActivity.kt
// import android.text.InputType
if (tipus == "age") {
    editText.inputType = InputType.TYPE_CLASS_NUMBER
}

RadioGroup + RadioButton — solo UNA opción (P3.1.a)

xmlcheckedButton = el marcado por defecto
<RadioGroup
    android:id="@+id/rgOperation"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:checkedButton="@id/rbAdd"
    app:layout_constraintTop_toBottomOf="@id/etNumber2">

    <RadioButton android:id="@+id/rbAdd"      android:text="Add" ... />
    <RadioButton android:id="@+id/rbSubtract" android:text="Subtract" ... />
</RadioGroup>
kotlinLeer el RadioButton marcado
val resultado = if (rbAdd.isChecked) num1 + num2 else num1 - num2
tvResult.text = resultado.toString()

// Reset
rbAdd.isChecked = true
// Alternativa: rgOperation.checkedRadioButtonId == R.id.rbAdd

CheckBox — varias opciones a la vez (P3.1.b)

kotlinwhen sin argumento = cadena de if/else
// Se ejecuta cada vez que se marca o desmarca
cbAdd.setOnCheckedChangeListener { _, _ -> updateButton() }
cbSubstract.setOnCheckedChangeListener { _, _ -> updateButton() }

private fun updateButton() {
    btnCalculate.text = when {
        cbAdd.isChecked && cbSubstract.isChecked -> "Add & Subtract"
        cbAdd.isChecked -> "Add"
        cbSubstract.isChecked -> "Subtract"
        else -> "Calculate"
    }
}
RadioButtonExcluyentes (1 de N). Van dentro de un RadioGroup.
CheckBoxIndependientes (0..N). No necesitan grupo.

Toast

kotlinToast
Toast.makeText(this, "Texto", Toast.LENGTH_SHORT).show()   // ¡no olvides .show()!
Toast.makeText(this, getString(R.string.toast_ocultar), Toast.LENGTH_LONG).show()

// Dentro de un object anónimo (Spinner) "this" no es la Activity:
Toast.makeText(this@MainActivity, "...", Toast.LENGTH_SHORT).show()

AlertDialog de confirmación (Ex2, Ex3, Ex4)

kotlinAlertDialog
// import androidx.appcompat.app.AlertDialog
AlertDialog.Builder(this)
    .setTitle("Confirmación")
    .setMessage("¿Estás seguro de que deseas salir?")
    .setPositiveButton("Sí") { _, _ -> finish() }
    .setNegativeButton("No", null)     // null = solo cierra el diálogo
    .show()

🔀Intents: navegar y pasar datos

Un Intent es un "mensaje" para abrir otra pantalla (o app). Hay 4 casos en tus prácticas:

A Abrir y ya
B Abrir + enviar datos
C Abrir y que me devuelva datos
D Intent implícito (web)

A · Abrir otra Activity y cerrarla (Ex1 Author)

kotlinIntent explícito
// MainActivity
val intent = Intent(this, AuthorActivity::class.java)
startActivity(intent)

// AuthorActivity → botón CLOSE
btnClose.setOnClickListener { finish() }   // destruye esta y vuelve a la anterior
⚠️ Ojo
Para volver usa finish(), no otro startActivity hacia la Main: eso apilaría pantallas (Main → Author → Main → Author…).

B · Enviar datos con putExtra (Ex2, Ex3)

kotlinEmisor — MainActivity (Ex3)
class MainActivity : AppCompatActivity() {
    // Claves en un companion object → no te equivocas al escribirlas
    companion object {
        const val USER_NAME = "USER_NAME"
        const val USER_SCORE = "USER_SCORE"
    }

    private fun openScoreActivity(user: String, score: Int) {
        val intent = Intent(this, ScoreActivity::class.java).apply {
            putExtra(USER_NAME, user)
            putExtra(USER_SCORE, score)
        }
        startActivity(intent)
    }
}
kotlinReceptor — ScoreActivity (Ex3)
// Receptor — ScoreActivity
val username = intent.getStringExtra(MainActivity.USER_NAME) ?: "User"
val score    = intent.getIntExtra(MainActivity.USER_SCORE, 0)   // 0 = valor por defecto

tvWelcome.text = "Welcome my lord, $username!"
tvScore.text = "Your score is $score"
getStringExtra(k)devuelve String? → usa ?: "valor" (operador Elvis)
getIntExtra(k, def)obliga a dar valor por defecto
getBooleanExtra / getDoubleExtraigual que Int, con defecto
.apply { }configura el objeto y lo devuelve (ahorra repetir intent.)

C · Que la 2ª pantalla DEVUELVA un dato (Ex4 Name or age) ⭐

Main: launcher.launch(intent)
Data: setResult(RESULT_OK, intent) + finish()
Main: se ejecuta la lambda del launcher
kotlinMainActivity — quien pide el dato
class MainActivity : AppCompatActivity() {

    private lateinit var textView3: TextView

    // Se declara como propiedad (fuera de onCreate)
    private val launcher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val tipus = result.data?.getStringExtra("TIPUS")
            val dada  = result.data?.getStringExtra("DADA")
            textView3.text = "The $tipus entered is $dada"
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        button.setOnClickListener {
            val intent = Intent(this, DataActivity::class.java)
            intent.putExtra("TIPUS", "name")
            launcher.launch(intent)          // en vez de startActivity
        }
    }
}
kotlinDataActivity — quien devuelve el dato
// SUBMIT: devolver el dato
private fun submit() {
    val resultat = Intent()
    resultat.putExtra(TIPUS, tipus)
    resultat.putExtra(DADA, editText.text.toString())
    setResult(Activity.RESULT_OK, resultat)
    finish()
}

// CANCEL: salir sin dato
private fun exitWithoutSaving() {
    setResult(Activity.RESULT_CANCELED)
    finish()
}
🎯 Para el examen
registerForActivityResult debe registrarse antes de que la Activity se cree → como propiedad de la clase (private val launcher = ...), nunca dentro de un click. Imports: androidx.activity.result.contract.ActivityResultContracts y android.app.Activity.

D · Intent implícito: abrir una web (Menús)

kotlinAndroid elige qué app lo abre (navegador)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://one.google.com/about/plans"))
startActivity(intent)

Interceptar el botón Atrás del sistema (Ex3)

kotlinScoreActivity — en onCreate
// import androidx.activity.OnBackPressedCallback
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        showConfirmationDialog()   // pregunta antes de salir
    }
})
💡 Truco
onBackPressed() está deprecated. La forma moderna es onBackPressedDispatcher.addCallback.

📋ListView y Spinner (Adapters)

Ambos muestran una lista de datos. El ArrayAdapter es el "traductor": convierte cada elemento de la lista en una fila visual.

Datos
List<String>
ArrayAdapter
+ layout de fila
ListView / Spinner
.adapter = adapter

Los datos (clase Java AmericanCountries)

javaDos listas paralelas: misma posición = mismo país
public class AmericanCountries {
    public static final List<String>  COUNTRIES = new ArrayList<>();
    public static final List<Integer> AREAS     = new ArrayList<>();

    static {                       // bloque estático: se ejecuta una vez
        COUNTRIES.add("Canadá");   AREAS.add(9984670);
        COUNTRIES.add("Brasil");   AREAS.add(8514877);
        ...
    }
}

Desde Kotlin se usan directamente: AmericanCountries.COUNTRIES[position]. Kotlin y Java conviven en el mismo proyecto sin problema.

ListView (P3.2)

xml0dp + constraints = ocupa el resto de pantalla
<ListView
    android:id="@+id/lvPaises"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintTop_toBottomOf="@id/tvSuperficie"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />
kotlinMainActivity.kt (P3.2)
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, AmericanCountries.COUNTRIES)
lvPaises.adapter = adapter

lvPaises.setOnItemClickListener { _, _, position, _ ->
    val pais = AmericanCountries.COUNTRIES[position]
    val superficie = AmericanCountries.AREAS[position]
    tvSuperficie.text = "$pais extension is $superficie km²"
}

Spinner (P3.3)

kotlinMainActivity.kt (P3.3)
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, AmericanCountries.COUNTRIES)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spPaises.adapter = adapter

spPaises.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        mostrarSuperficie(position)
    }
    override fun onNothingSelected(parent: AdapterView<*>?) { }
}
ListViewSpinner
AspectoLista entera visible, con scrollDesplegable (combo)
Layout filasimple_list_item_1simple_spinner_item + setDropDownViewResource(simple_spinner_dropdown_item)
ListenersetOnItemClickListener { _, _, position, _ -> } (lambda)onItemSelectedListener = object : ... (2 métodos obligatorios)
Al arrancarNo hay nada pulsadoSe llama solo a onItemSelected(0) → ya muestra el primero
⚠️ Ojo
En el object del Spinner, this es el listener, no la Activity → usa this@MainActivity para el Toast.
💡 Truco
Si te piden un array fijo en vez de la clase Java: en Kotlin val paises = arrayOf("A","B") o en res/values/strings.xml con <string-array> y resources.getStringArray(R.array.paises).

🧩Kotlin útil y edge-to-edge

Kotlin que sale en las prácticas

kotlinKotlin
// Arrays paralelos (Ex3)
private val users  = arrayOf("gandalf", "frodo", "saruman")
private val points = intArrayOf(315, 222, 489)

// Buscar posición ignorando mayúsculas (-1 si no está)
val i = users.indexOfFirst { it.equals(user, ignoreCase = true) }
return if (i != -1) points[i] else 0        // if como expresión

// Elvis: valor por defecto si es null
val name = intent.getStringExtra("K") ?: "User"

// Safe call: solo llama si no es null
supportActionBar?.hide()
result.data?.getStringExtra("DADA")

// Plantillas de String
"Add: ${add(num1, num2)}\n"

// Constantes de clase
companion object { const val USER_NAME = "USER_NAME" }

// Argumentos de lambda que no usas → _
cbAdd.setOnCheckedChangeListener { _, _ -> updateButton() }

Edge-to-edge (código que genera Android Studio)

Las plantillas nuevas ponen enableEdgeToEdge() y un listener de insets. Necesita que el layout raíz tenga android:id="@+id/main".

kotlinEx2 / Ex3
enableEdgeToEdge()
setContentView(R.layout.activity_main)

ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
    val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
    insets
}
⚠️ Ojo
Si borras el android:id="@+id/main" del layout pero dejas este código → NullPointerException al arrancar. O borras los dos, o ninguno.

🗂️Ficha de cada ejercicio

Qué hace cada app y qué conceptos tiene dentro — para saber cuál abrir en el examen.

Ex1

Author

2 pantallas: botón AUTHOR → pantalla con nombre del autor → CLOSE vuelve.

MainActivityAuthorActivity (layout activity_info.xml)
  • Intent explícito + startActivity
  • finish() para volver sin apilar
  • supportActionBar?.title
  • Cadena vertical packed
  • Patrón initViews / setupListeners
Ex2

Password

Login: usuario + contraseña. Si es abc123 abre Welcome con el nombre; si no, muestra error rojo.

MainActivityWelcomeActivity
  • EditText textPassword
  • trim() + isEmpty() + .error
  • visibility GONE/VISIBLE
  • companion object con claves
  • putExtra / getStringExtra ?:
  • AlertDialog al cerrar
  • enableEdgeToEdge + insets
Ex3

Lord of the rings

Escribes un nombre (gandalf, frodo, saruman); el botón aparece al escribir; la 2ª pantalla muestra su puntuación.

MainActivityScoreActivity
  • doOnTextChanged → botón visible
  • Arrays paralelos + indexOfFirst(ignoreCase)
  • putExtra String + Int / getIntExtra
  • OnBackPressedCallback
  • AlertDialog en botón y en Atrás
  • fontFamily personalizada
Ex4

Name or age

Botones Name / Age abren la misma DataActivity; el usuario escribe y SUBMIT devuelve el dato a Main, que lo pinta.

MainActivityDataActivity
  • registerForActivityResult
  • launcher.launch(intent)
  • setResult(RESULT_OK / RESULT_CANCELED)
  • Una Activity reutilizada con un extra TIPUS
  • inputType = TYPE_CLASS_NUMBER
  • AlertDialog solo si hay texto
  • Botones en la misma fila (Start_toEndOf)
P3.1.a

Suma o resta

Dos números + RadioButton Add/Subtract + botón → resultado. Clear lo resetea.

MainActivity
  • RadioGroup + checkedButton
  • rbAdd.isChecked
  • toDoubleOrNull + Toast
  • return@setOnClickListener
  • ScrollView
  • inputType numberDecimal|numberSigned
  • freezesText
P3.1.b

Suma i resta

Igual pero con CheckBox: puedes hacer suma, resta o ambas. El texto del botón cambia según lo marcado.

MainActivity
  • CheckBox + setOnCheckedChangeListener
  • when sin argumento
  • Funciones add()/substract()
  • Concatenar resultado con \n + trim()
  • Validar que hay alguna opción marcada
P3.2

Countries ListView

Lista de países de América; al pulsar uno se muestra su superficie arriba.

MainActivityAmericanCountries.java
  • ListView 0dp
  • ArrayAdapter + simple_list_item_1
  • setOnItemClickListener (position)
  • Listas paralelas Java static
  • Interoperabilidad Kotlin ↔ Java
P3.3

Countries Spinner

Lo mismo con un desplegable Spinner.

MainActivityAmericanCountries.java
  • Spinner
  • simple_spinner_item + setDropDownViewResource
  • object : AdapterView.OnItemSelectedListener
  • onItemSelected + onNothingSelected
  • this@MainActivity
Pt1.2

Menús (MenuToolbar)

Toolbar propia con iconos Perfil y Adjuntar, ⋮ con 'Añadir amigos' (abre AddActivity) y 'Cambiar a Pro' (abre web). Botones ocultar/mostrar Toolbar.

MainActivityAddActivityres/menu/menu_main.xmlthemes.xmlvalues-en / values-fr
  • Tema NoActionBar + Toolbar
  • setSupportActionBar
  • onCreateOptionsMenu / onOptionsItemSelected
  • showAsAction ifRoom / never
  • Intent.ACTION_VIEW + Uri.parse
  • supportActionBar?.hide()/show()
  • Strings en 3 idiomas + getString
  • popupTheme

✅Checklist y errores típicos

Crear una pantalla nueva en 30 segundos

  1. File ▸ New ▸ Activity ▸ Empty Views Activity (NO "Empty Activity", que es Compose).
  2. Diseña el XML: pon id a todo lo que vayas a usar desde Kotlin.
  3. En Kotlin: lateinit var → findViewById → listeners.
  4. Desde la pantalla anterior: Intent + startActivity (o launcher.launch).
  5. Comprueba que está en el Manifest.

Errores típicos

La app se cierra al abrir una pantallaActivity no declarada en el Manifest → ActivityNotFoundException.
NullPointerException en findViewByIdid mal escrito, findViewById antes de setContentView, o estás buscando un id de otro layout.
NumberFormatExceptionHas usado toDouble() con el campo vacío → usa toDoubleOrNull().
El Toast no saleFalta .show().
El menú no apareceNo has hecho setSupportActionBar(toolbar), falta onCreateOptionsMenu, o devuelve false.
"This Activity already has an action bar"El tema no es NoActionBar y usas setSupportActionBar.
Los iconos salen en el ⋮ y no en la barraandroid:showAsAction en vez de app:showAsAction.
El dato devuelto no llegaHas usado startActivity en vez de launcher.launch, o no has hecho setResult(RESULT_OK, intent) antes de finish().
getStringExtra devuelve nullLa clave del putExtra y del get no coinciden → usa constantes en companion object.
Pantallas apiladas al volverVolver con finish(), no con un nuevo Intent.
Pierdes el resultado al girarandroid:freezesText="true" en el TextView.
Import equivocadoandroidx.appcompat.widget.Toolbar y androidx.appcompat.app.AlertDialog. Alt+Enter para importar.

Atajos de Android Studio

Alt+Enterimportar / arreglo rápido
Ctrl+Ooverride methods (onCreateOptionsMenu…)
Ctrl+Alt+Lformatear código
Shift+F6renombrar (id, variable) en todo el proyecto
Shift+F10ejecutar app
Ctrl+Shift+Fbuscar en todo el proyecto (busca en tus prácticas)
Ctrl+clicir a la definición
Logcatfiltra por "FATAL" para ver por qué peta

🎮Quiz estilo Kahoot

Ponte a prueba. Las respuestas se barajan cada vez.