Cómo conectarse a la red

A fin de 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 (CA) personalizadas o restringir el conjunto de CA 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. En este tema, se usa la biblioteca cliente HTTP de Retrofit, que te permite crear un cliente HTTP de forma declarativa. Además, Retrofit admite la serialización de cuerpos de solicitud y la deserialización de cuerpos de respuesta automáticas.

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 mediante TLS. La API de DnsResolver proporciona una resolución asíncrona genérica, que te permite buscar SRV, NAPTR y otros tipos de registro. Ten en cuenta que la app realizará el análisis de la respuesta.

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.

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. De lo contrario, se arrojará una NetworkOnMainThreadException. En la clase UserRepository que se muestra 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().

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. Cualquier dato que no se guarde en el estado de la instancia del fragmento o de la actividad, que debería contener solo pequeñas cantidades de datos, se pierde, y es posible que debas volver a realizar las solicitudes de red.

Puedes usar un objeto ViewModel para asegurarte de que se conserven los datos después de que se realicen cambios de configuración. ViewModel es un componente diseñado para almacenar y administrar datos relacionados con la IU de una 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 mediante 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 will move 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: