Cómo conectarse a una red

Para realizar operaciones de red en tu aplicación, el manifiesto debe incluir los siguientes permisos:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Prácticas recomendadas para crear una comunicación de red segura

Antes de agregar funcionalidades de red en tu app, debes asegurarte de que los datos y la información que esta contenga permanezcan seguros cuando se transmitan a través de una red. Para hacerlo, sigue estas prácticas recomendadas de seguridad de redes:

  • Minimiza la cantidad de datos del usuario sensibles o personales que se transmiten a través de la red.
  • Envía todo el tráfico de red de tu app a través de SSL.
  • Puedes crear una configuración de seguridad de la red, que le permita a tu app confiar en autoridades certificadoras (CAs) personalizadas o restringir el conjunto de CAs del sistema en las que confía para lograr una comunicación segura.

Si quieres obtener más información para aplicar los principios de una red segura, consulta las sugerencias sobre la seguridad de redes.

Cómo elegir un cliente HTTP

La mayoría de las apps conectadas a redes utilizan HTTP para enviar y recibir datos. La plataforma de Android incluye el cliente HttpsURLConnection, que admite TLS, cargas y descargas de transmisión, tiempos de espera configurables, IPv6 y reducciones de conexiones.

También están disponibles las bibliotecas de terceros que ofrecen APIs de nivel superior para operaciones de red. Estas admiten varias prácticas funciones, como la serialización de los cuerpos de solicitud y la deserialización de los cuerpos de respuesta.

  • Retrofit: Un cliente HTTP de tipo seguro para la JVM de Square, compilado sobre OkHttp. Retrofit te permite crear una interfaz cliente de forma declarativa y es compatible con varias bibliotecas de serialización.
  • Ktor: Es un cliente HTTP de JetBrains compilado completamente para Kotlin con tecnología de corrutinas. Ktor admite varios motores, serializadores y plataformas.

Cómo resolver consultas de DNS

Los dispositivos que ejecutan Android 10 (nivel de API 29) y versiones posteriores brindan compatibilidad nativa con búsquedas de DNS especializadas a través de búsquedas de texto simple y el modo de DNS a través de TLS. La API de DnsResolver proporciona una resolución asíncrona genérica, que te permite buscar SRV, NAPTR y otros tipos de registro. El análisis de la respuesta se realiza en la app.

En los dispositivos que ejecutan Android 9 (nivel de API 28) y versiones anteriores, la resolución de DNS de la plataforma solo admite registros A y AAAA, lo que permite buscar las direcciones IP asociadas con un nombre, pero no admite ningún otro tipo de registro.

Para apps basadas en NDK, consulta android_res_nsend.

Cómo encapsular las operaciones de red con un repositorio

A fin de simplificar el proceso de las operaciones de red y reducir la duplicación de código en varias partes de la app, puedes usar el patrón de diseño del repositorio. Un repositorio es una clase que procesa operaciones de datos y proporciona una abstracción de API limpia a partir de algunos datos o recursos específicos.

Puedes usar Retrofit para declarar una interfaz que especifique el método HTTP, URL, argumentos y el tipo de respuesta para las operaciones de red, como se muestra en el siguiente ejemplo:

Kotlin

interface UserService {
    @GET("/users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}

Java

public interface UserService {
    @GET("/user/{id}")
    Call<User> getUserById(@Path("id") String id);
}

En una clase de repositorio, las funciones pueden encapsular operaciones de red y exponer sus resultados. Esta encapsulación garantiza que los componentes que invocan al repositorio no necesiten saber cómo se almacenan los datos. Los cambios futuros que puedan hacerse sobre la forma en que se almacenan los datos también se aíslan de la clase del repositorio. Por ejemplo, puedes tener un cambio remoto, como una actualización a los extremos de la API, o tal vez quieras implementar el almacenamiento en caché local.

Kotlin

class UserRepository constructor(
    private val userService: UserService
) {
    suspend fun getUserById(id: String): User {
        return userService.getUser(id)
    }
}

Java

class UserRepository {
    private UserService userService;

    public UserRepository(
            UserService userService
    ) {
        this.userService = userService;
    }

    public Call<User> getUserById(String id) {
        return userService.getUser(id);
    }
}

Para evitar crear una IU sin respuesta, te recomendamos que no lleves a cabo operaciones de red en el subproceso principal. De forma predeterminada, Android requiere que realices las operaciones de red en un subproceso de IU que no sea el principal. Si intentas realizar operaciones de red en el subproceso principal, se genera una NetworkOnMainThreadException.

En el ejemplo de código anterior, no se activa realmente la operación de red. El llamador del UserRepository debe implementar el subproceso mediante corrutinas o con la función enqueue(). Para obtener más información, consulta el codelab Cómo obtener datos de Internet, que muestra cómo implementar subprocesos con corrutinas de Kotlin.

Cómo conservar la actividad después de hacer cambios en la configuración

Cuando se produce un cambio de configuración, como una rotación de pantalla, se destruye la actividad o el fragmento y, luego, se vuelve a crear. Se perderán todos los datos que no se guardaron en el estado de la instancia del fragmento o la actividad, que solo puede contener pequeñas cantidades de datos. Si esto ocurre, es posible que debas volver a realizar las solicitudes de red.

Puedes usar un ViewModel para permitir que tus datos sobrevivan a los cambios de configuración. Se diseñó el componente ViewModel para almacenar y administrar datos relacionados con la IU de manera optimizada para los ciclos de vida. Con el UserRepository creado anteriormente, el objeto ViewModel puede llevar a cabo las solicitudes de red necesarias y proporcionar el resultado a tu fragmento o actividad a través de LiveData:

Kotlin

class MainViewModel constructor(
    savedStateHandle: SavedStateHandle,
    userRepository: UserRepository
) : ViewModel() {
    private val userId: String = savedStateHandle["uid"] ?:
        throw IllegalArgumentException("Missing user ID")

    private val _user = MutableLiveData<User>()
    val user = _user as LiveData<User>

    init {
        viewModelScope.launch {
            try {
                // Calling the repository is safe as it moves execution off
                // the main thread
                val user = userRepository.getUserById(userId)
                _user.value = user
            } catch (error: Exception) {
                // Show error message to user
            }

        }
    }
}

Java

class MainViewModel extends ViewModel {

    private final MutableLiveData<User> _user = new MutableLiveData<>();
    LiveData<User> user = (LiveData<User>) _user;

    public MainViewModel(
            SavedStateHandle savedStateHandle,
            UserRepository userRepository
    ) {
        String userId = savedStateHandle.get("uid");
        Call<User> userCall = userRepository.getUserById(userId);
        userCall.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                if (response.isSuccessful()) {
                    _user.setValue(response.body());
                }
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // Show error message to user
            }
        });
    }
}

Para obtener más información sobre este tema, consulta las siguientes guías: